@hanzo/rooms 0.1.27 → 0.1.29

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/Contacts.js CHANGED
@@ -80,7 +80,7 @@ function Note({ children }) {
80
80
  * The directory itself.
81
81
  *
82
82
  * SPLIT OUT because `usePeople` needs an owner and a hook cannot be called
83
- * conditionally — the same reason Home and Vibe split theirs.
83
+ * conditionally — the same reason Home splits its own.
84
84
  */
85
85
  function Roster({ owner }) {
86
86
  const ai = useAi();
@@ -95,7 +95,7 @@ export function OrgSwitcherDropdown({ open, onClose, activeOrgId, onSelect, alig
95
95
  onSelect(o.id);
96
96
  onClose();
97
97
  }, width: "100%", justify: "space-between", px: 10, py: 6, bg: isSelected ? '$hover' : 'transparent', children: [_jsxs(XStack, { items: "center", gap: 10, minW: 0, shrink: 1, children: [_jsx(Text, { width: 24, height: 24, rounded: 6, bg: "$raised", display: "flex", items: "center", justify: "center", fontSize: "$2", fontWeight: "700", color: "$ink", shrink: 0, overflow: "hidden", children: o.logo ? (_jsx(View, { render: _jsx("img", { src: o.logo, alt: "" }), width: "100%", height: "100%", objectFit: "cover" })) : (o.displayName.toLowerCase().includes('hanzo') || o.name.toLowerCase().includes('hanzo')) ? (_jsx(HanzoMark, { size: 14 })) : (o.name.slice(0, 3).toUpperCase()) }), _jsxs(YStack, { minW: 0, shrink: 1, items: "flex-start", children: [_jsx(Text, { fontSize: "$2", fontWeight: isSelected ? '600' : '500', color: "$ink", whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis", children: o.displayName }), o.role ? (_jsx(Text, { fontSize: FLOOR, color: "$faint", children: o.role })) : null] })] }), isSelected ? _jsx(Check, { size: 14, color: "currentColor" }) : null] }, o.id));
98
- }), _jsx(View, { height: 1, bg: "$borderColor", my: 4 }), _jsx(Button, { asChild: true, ...LINK, children: _jsxs("a", { href: iam(), target: "_blank", rel: "noreferrer", children: [_jsx(Plus, { size: 14 }), "Create Organization"] }) }), _jsx(Button, { asChild: true, ...LINK, children: _jsxs("a", { href: site("/account/organization"), children: [_jsx(Settings, { size: 14 }), "Organization Settings"] }) })] }));
98
+ }), _jsx(View, { height: 1, bg: "$borderColor", my: 4 }), _jsx(Button, { asChild: true, ...LINK, children: _jsxs("a", { href: iam(), target: "_blank", rel: "noreferrer", children: [_jsx(Plus, { size: 14 }), "Create Organization"] }) }), _jsx(Button, { asChild: true, ...LINK, children: _jsxs("a", { href: site("/settings/organization"), children: [_jsx(Settings, { size: 14 }), "Organization settings"] }) })] }));
99
99
  }
100
100
  /**
101
101
  * THE WORKSPACE YOU ARE STANDING IN, and the way to another one.
package/dist/lib/api.d.ts CHANGED
@@ -5,7 +5,7 @@
5
5
  * else could reach it: seven other modules wrote their own
6
6
  * `process.env.NEXT_PUBLIC_HANZO_API_URL || 'https://api.hanzo.ai'` — reach.ts,
7
7
  * team.ts, coding.ts, referrals.ts, useTiers.ts, Settings.tsx, Directory.tsx,
8
- * Beluga.tsx — and every one of them got the local case wrong in the same way.
8
+ * and every one of them got the local case wrong in the same way.
9
9
  *
10
10
  * THE LOCAL CASE IS THE WHOLE POINT. api.hanzo.ai admits an origin by allowlist
11
11
  * and by an https, portless DNS proof; localhost satisfies neither, so a
package/dist/lib/api.js CHANGED
@@ -5,7 +5,7 @@
5
5
  * else could reach it: seven other modules wrote their own
6
6
  * `process.env.NEXT_PUBLIC_HANZO_API_URL || 'https://api.hanzo.ai'` — reach.ts,
7
7
  * team.ts, coding.ts, referrals.ts, useTiers.ts, Settings.tsx, Directory.tsx,
8
- * Beluga.tsx — and every one of them got the local case wrong in the same way.
8
+ * and every one of them got the local case wrong in the same way.
9
9
  *
10
10
  * THE LOCAL CASE IS THE WHOLE POINT. api.hanzo.ai admits an origin by allowlist
11
11
  * and by an https, portless DNS proof; localhost satisfies neither, so a
package/dist/lib/home.js CHANGED
@@ -34,7 +34,7 @@ import { site } from '../where.js';
34
34
  * other page of this app is, and the three doors that offer it (the empty
35
35
  * room's tile, the Create menu, the sidebar's row) all read this one line.
36
36
  */
37
- export const invite = () => site('/account/organization');
37
+ export const invite = () => site('/settings/organization/members');
38
38
  /** The org's projects, newest first. */
39
39
  export async function projects(client, signal) {
40
40
  const rows = await client.http.json({ path: '/v1/projects', signal });
package/dist/lib/host.js CHANGED
@@ -70,8 +70,8 @@ const SLUG = /^[a-z0-9](?:[a-z0-9-]{0,38}[a-z0-9])?$/;
70
70
  * another address.
71
71
  */
72
72
  export const dev = (ref) => site(!ref ? DEV : SESSION.test(ref) ? `${DEV}?run=${ref}` : SLUG.test(ref) ? `${DEV}?project=${ref}` : DEV);
73
- /** The routes under app/(app) the apex keeps: the app — /chat, a chat shared by link, and /dev — and three surfaces that are not the workspace's. */
74
- const KEPT = new Set(['/chat', '/chat/shared', '/dev', '/vibe', '/beluga', '/dashboard']);
73
+ /** The routes under app/(app) the apex keeps: the app — /chat, a chat shared by link, and /dev. */
74
+ const KEPT = new Set(['/chat', '/chat/shared', '/dev']);
75
75
  /** Settings, and every page under it: the person's and the organization's, served wherever the app is. */
76
76
  const SETTINGS = '/settings';
77
77
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hanzo/rooms",
3
- "version": "0.1.27",
3
+ "version": "0.1.29",
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",
package/dist/Beluga.d.ts DELETED
@@ -1,12 +0,0 @@
1
- /**
2
- * Beluga Fullscreen Ocean AI Studio
3
- *
4
- * Built with @hanzo/gui native components and responsive CSS Grid:
5
- * - Desktop (>= 1440px) / Laptop (1024px - 1439px): 3-column grid (Left Prompts, Center Ocean Canvas, Right Telemetry)
6
- * - Tablet (768px - 1023px): 2-column grid (Center Ocean Canvas + Right Telemetry)
7
- * - Mobile (< 768px): 1-column grid with touch drawers
8
- *
9
- * Uses @hanzo/gui native components exclusively (View, XStack, YStack, Text, Button, etc.).
10
- */
11
- import React from 'react';
12
- export declare function BelugaStudio(): React.JSX.Element;
package/dist/Beluga.js DELETED
@@ -1,152 +0,0 @@
1
- 'use client';
2
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
- /**
4
- * Beluga Fullscreen Ocean AI Studio
5
- *
6
- * Built with @hanzo/gui native components and responsive CSS Grid:
7
- * - Desktop (>= 1440px) / Laptop (1024px - 1439px): 3-column grid (Left Prompts, Center Ocean Canvas, Right Telemetry)
8
- * - Tablet (768px - 1023px): 2-column grid (Center Ocean Canvas + Right Telemetry)
9
- * - Mobile (< 768px): 1-column grid with touch drawers
10
- *
11
- * Uses @hanzo/gui native components exclusively (View, XStack, YStack, Text, Button, etc.).
12
- */
13
- import { useState, useEffect } from 'react';
14
- import { useRooms } from './host.js';
15
- import { Bot, Sparkles, Volume2, VolumeX, ArrowLeft, ArrowUp, Activity, } from 'lucide-react';
16
- import { View, XStack, YStack, Text, Button } from '@hanzo/gui';
17
- import { bearer, scope } from './lib/session.js';
18
- import { api } from './lib/api.js';
19
- import { Orgs } from './Orgs.js';
20
- import { mix } from './lib/mix.js';
21
- const EMOTIONS = {
22
- happy: { name: 'Happiness', emoji: '😊', desc: 'Joyful bright clicks and radiant ocean bubbles.' },
23
- playful: { name: 'Playful', emoji: '🐬', desc: 'Playful spiraling swim with rhythmic echolocation pulses.' },
24
- love: { name: 'Love', emoji: '💙', desc: 'Deep empathetic resonance and social connection.' },
25
- curious: { name: 'Curiosity', emoji: '🤔', desc: 'Curious sonar ping focused on analyzing data.' },
26
- calm: { name: 'Calmness', emoji: '🌊', desc: 'Tranquil resting glide through deep arctic currents.' },
27
- awe: { name: 'Awe', emoji: '🌟', desc: 'Wide acoustic aperture detecting novel patterns.' },
28
- };
29
- const QUICK_PROMPTS = [
30
- 'Origin Eggs 🥚',
31
- 'Endangered Species 🐅',
32
- 'ZenLM Neural AI 🧠',
33
- ];
34
- const IDLE_THOUGHTS = [
35
- 'Did you know belugas use echolocation to navigate underwater caves? 🐬',
36
- 'I am ready when you are! Ask me anything about Hanzo Cloud or conservation! ✨',
37
- 'The ocean is calm today... What shall we build or discover? 🌊',
38
- 'Echolocation ping sent! Waiting for your signal... 📡',
39
- 'Hatching an Origin Egg sounds exciting today! 🥚',
40
- ];
41
- /** The room's dark glass: the canvas shows through blurred, under a lit top
42
- * edge and a soft drop. The fill is each surface's own. */
43
- const LENS = {
44
- backdropFilter: 'blur(24px) saturate(190%) contrast(102%)',
45
- boxShadow: 'inset 0 1px 0 0 var(--white-14), inset 0 0 0 1px var(--white-03), 0 8px 32px -4px color-mix(in srgb, var(--pure-black) 40%, transparent)',
46
- };
47
- /** The ground under the lens: the page's darkest neutral at a share of itself. */
48
- const glass = (share) => mix('var(--neutral-950)', share, 'srgb');
49
- function BelugaStudioInner() {
50
- const { Link } = useRooms();
51
- const [leftOpen, setLeftOpen] = useState(true);
52
- const [rightOpen, setRightOpen] = useState(true);
53
- const [muted, setMuted] = useState(false);
54
- const [currentEmotion, setCurrentEmotion] = useState('playful');
55
- const [input, setInput] = useState('');
56
- const [busy, setBusy] = useState(false);
57
- const [idleThought, setIdleThought] = useState(IDLE_THOUGHTS[0]);
58
- const [mobileDrawer, setMobileDrawer] = useState('none');
59
- const [messages, setMessages] = useState([
60
- {
61
- id: '1',
62
- role: 'assistant',
63
- content: 'Hello! I am Blue the Beluga, powered by ZenLM on the Hanzo Cloud. How can I assist your team with agentic coding, conservation, or ocean acoustics today?',
64
- emotion: 'playful',
65
- },
66
- ]);
67
- useEffect(() => {
68
- const interval = setInterval(() => {
69
- const idx = Math.floor(Math.random() * IDLE_THOUGHTS.length);
70
- setIdleThought(IDLE_THOUGHTS[idx]);
71
- }, 12000);
72
- return () => clearInterval(interval);
73
- }, []);
74
- const handleSend = async (textToSend) => {
75
- const text = (textToSend || input).trim();
76
- if (!text || busy)
77
- return;
78
- setInput('');
79
- setBusy(true);
80
- const userMsg = {
81
- id: `u_${Date.now()}`,
82
- role: 'user',
83
- content: text,
84
- emotion: 'curious',
85
- };
86
- setMessages((prev) => [...prev, userMsg]);
87
- const nextEmotion = text.toLowerCase().includes('egg')
88
- ? 'curious'
89
- : text.toLowerCase().includes('build') || text.toLowerCase().includes('code')
90
- ? 'awe'
91
- : 'happy';
92
- setCurrentEmotion(nextEmotion);
93
- try {
94
- const tok = bearer();
95
- const res = await fetch(`${api()}/v1/chat/completions`, {
96
- method: 'POST',
97
- headers: {
98
- 'Content-Type': 'application/json',
99
- ...(tok ? scope() : {}),
100
- },
101
- body: JSON.stringify({
102
- model: 'zen',
103
- messages: [
104
- {
105
- role: 'system',
106
- content: "You are Blue the Beluga, Hanzo's bioacoustic ocean AI and neural sonar intelligence agent. You provide ocean telemetry and marine analysis. Always start with a brief italicized acoustic sensory description like *high-frequency clicks* or *curious sonar whistle* reflecting the mood.",
107
- },
108
- ...messages.slice(-6).map((m) => ({ role: m.role, content: m.content })),
109
- { role: 'user', content: text },
110
- ],
111
- }),
112
- });
113
- if (!res.ok) {
114
- const body = (await res.json().catch(() => ({})));
115
- throw new Error(body?.error?.message || body?.msg || `Cluster returned HTTP ${res.status}`);
116
- }
117
- const data = (await res.json());
118
- const reply = data?.choices?.[0]?.message?.content;
119
- if (!reply)
120
- throw new Error('The model returned an empty reply.');
121
- const botMsg = {
122
- id: `a_${Date.now()}`,
123
- role: 'assistant',
124
- content: reply,
125
- emotion: nextEmotion,
126
- };
127
- setMessages((prev) => [...prev, botMsg]);
128
- }
129
- catch (err) {
130
- const message = err instanceof Error ? err.message : String(err);
131
- const botMsg = {
132
- id: `a_${Date.now()}`,
133
- role: 'assistant',
134
- content: `*Acoustic cluster refraction*\n\n${message}`,
135
- emotion: 'curious',
136
- };
137
- setMessages((prev) => [...prev, botMsg]);
138
- }
139
- finally {
140
- setBusy(false);
141
- setMobileDrawer('none');
142
- }
143
- };
144
- return (_jsxs(YStack, { "data-beluga": true, flex: 1, minH: 0, height: "100vh", width: "100%", maxW: "100%", overflowX: "auto", overflowY: "hidden", bg: "var(--pure-black)", pb: 56, children: [_jsxs(XStack, { height: 50, items: "center", justify: "space-between", px: "$3.5", borderBottomWidth: 1, borderColor: "var(--white-08)", bg: "var(--neutral-950)", z: 30, shrink: 0, children: [_jsxs(XStack, { items: "center", gap: "$2.5", children: [_jsx(Link, { href: "/vibe", children: _jsx(XStack, { items: "center", justify: "center", width: 28, height: 28, rounded: 8, bg: "var(--neutral-900)", borderWidth: 1, borderColor: "var(--white-10)", hoverStyle: { bg: 'var(--neutral-800)' }, children: _jsx(ArrowLeft, { size: 14, color: "var(--neutral-400)" }) }) }), _jsxs(XStack, { items: "center", gap: "$2", children: [_jsx(Bot, { size: 15, color: "$ink" }), _jsx(Text, { render: "h1", fontSize: "$2", fontWeight: "700", color: "var(--pure-white)", children: "Blue the Beluga" }), _jsx(Text, { fontSize: 11, color: "var(--neutral-600)", children: "|" }), _jsx(Text, { fontSize: 11, color: "var(--neutral-400)", children: "ZenLM Neural Studio" })] })] }), _jsxs(XStack, { items: "center", gap: "$2", children: [_jsxs(XStack, { $md: { display: 'none' }, gap: "$1.5", children: [_jsx(Button, { size: "$2", height: 28, px: "$2", rounded: 6, bg: mobileDrawer === 'prompts' ? 'var(--neutral-800)' : 'var(--neutral-900)', onPress: () => setMobileDrawer(mobileDrawer === 'prompts' ? 'none' : 'prompts'), children: _jsx(Sparkles, { size: 12, color: "$ink" }) }), _jsx(Button, { size: "$2", height: 28, px: "$2", rounded: 6, bg: mobileDrawer === 'telemetry' ? 'var(--neutral-800)' : 'var(--neutral-900)', onPress: () => setMobileDrawer(mobileDrawer === 'telemetry' ? 'none' : 'telemetry'), children: _jsx(Activity, { size: 12, color: "$ink" }) })] }), _jsx(View, { cursor: "pointer", onPress: () => setMuted(!muted), p: 6, rounded: 8, bg: "var(--neutral-900)", borderWidth: 1, borderColor: "var(--white-08)", hoverStyle: { bg: 'var(--neutral-800)' }, children: muted ? _jsx(VolumeX, { size: 14, color: "var(--neutral-400)" }) : _jsx(Volume2, { size: 14, color: "var(--neutral-400)" }) }), _jsx(Button, { display: "none", $md: { display: 'flex' }, size: "$2", height: 28, px: "$3", rounded: 8, bg: "var(--neutral-900)", borderColor: "var(--white-10)", borderWidth: 1, onPress: () => setRightOpen(!rightOpen), children: _jsxs(XStack, { items: "center", gap: "$1.5", children: [_jsx(Activity, { size: 12, color: "$ink" }), _jsx(Text, { fontSize: 11, fontWeight: "600", color: "var(--neutral-200)", children: "Telemetry" })] }) })] })] }), _jsxs(View, { flex: 1, minH: 0, width: "100%", height: "100%", overflow: "hidden", display: "grid", gridTemplateRows: "100%", gridTemplateColumns: "100%", $md: { gridTemplateColumns: 'minmax(0, 1fr) 280px' }, $lg: { gridTemplateColumns: '280px minmax(0, 1fr) 280px' }, children: [_jsxs(YStack, { height: "100%", bg: "var(--neutral-950)", borderRightWidth: 1, borderColor: "var(--white-08)", minW: 0, display: mobileDrawer === 'prompts' ? 'flex' : 'none', "$max-md": { position: 'absolute', t: 0, r: 0, b: 0, l: 0, z: 'var(--z-dock)' }, $md: { display: 'none' }, $lg: { display: 'flex' }, children: [_jsx(XStack, { height: 40, items: "center", justify: "space-between", px: "$3.5", borderBottomWidth: 1, borderColor: "var(--white-06)", children: _jsx(Text, { fontSize: 11, fontWeight: "700", color: "var(--neutral-500)", textTransform: "uppercase", children: "Quick Ocean Prompts" }) }), _jsxs(YStack, { flex: 1, overflowY: "auto", overflowX: "hidden", p: "$3.5", gap: "$4", children: [_jsx(YStack, { gap: "$1.5", children: QUICK_PROMPTS.map((p, i) => (_jsx(View, { cursor: "pointer", onPress: () => handleSend(p), p: "$2.5", rounded: 8, bg: "var(--neutral-900)", borderWidth: 1, borderColor: "var(--white-06)", hoverStyle: { bg: 'var(--neutral-800)' }, children: _jsx(Text, { fontSize: 12, color: "var(--neutral-300)", children: p }) }, i))) }), _jsxs(YStack, { p: "$3", rounded: 12, bg: "var(--white-08)", borderWidth: 1, borderColor: "var(--white-20)", gap: "$1.5", children: [_jsxs(XStack, { items: "center", gap: "$1.5", children: [_jsx(Sparkles, { size: 13, color: "$ink" }), _jsx(Text, { fontSize: 11, fontWeight: "700", color: "$ink", children: "Ocean Thought" })] }), _jsx(Text, { fontSize: 11, color: "var(--neutral-300)", lineHeight: 16, children: idleThought })] })] })] }), _jsxs(YStack, { height: "100%", bg: "var(--pure-black)", position: "relative", items: "center", justify: "space-between", p: "$4", minW: 0, children: [_jsxs(YStack, { flex: 1, items: "center", justify: "center", width: "100%", children: [_jsx(YStack, { width: 220, height: 220, rounded: 110, borderWidth: 1, borderColor: "var(--white-30)", items: "center", justify: "center", shadowColor: "var(--white-20)", shadowRadius: 50, children: _jsx(Text, { fontSize: 72, children: "\uD83D\uDC2C" }) }), _jsx(View, { ...LENS, mt: "$4", maxW: 520, width: "100%", px: "$3.5", py: "$2.5", rounded: 14, bg: glass(85), borderWidth: 1, borderColor: "var(--white-10)", children: _jsx(Text, { fontSize: 12, color: "var(--neutral-200)", text: "center", fontFamily: "$mono", lineHeight: 18, children: messages[messages.length - 1]?.content }) })] }), _jsx(YStack, { width: "100%", maxW: 600, z: 20, children: _jsxs(XStack, { ...LENS, items: "center", bg: glass(90), borderWidth: 1, borderColor: "var(--white-14)", rounded: 14, p: "$2", children: [_jsx(Text, { render: _jsx("input", { type: "text", value: input, onChange: (e) => setInput(e.target.value), onKeyDown: (e) => e.key === 'Enter' && handleSend(), placeholder: "Ask Blue the Beluga anything or send acoustic signal..." }), flex: 1, bg: "transparent", borderWidth: 0, color: "var(--pure-white)", fontSize: "$2", outlineStyle: "none", px: 10, py: 4 }), _jsx(Button, { size: "$2", bg: "var(--neutral-50)", rounded: 10, height: 30, px: "$2.5", onPress: () => handleSend(), disabled: !input.trim() || busy, opacity: !input.trim() || busy ? 0.3 : 1, hoverStyle: { bg: 'var(--neutral-200)' }, children: _jsx(ArrowUp, { size: 14, color: "var(--neutral-950)" }) })] }) })] }), _jsxs(YStack, { height: "100%", bg: "var(--neutral-950)", borderLeftWidth: 1, borderColor: "var(--white-08)", minW: 0, display: mobileDrawer === 'telemetry' ? 'flex' : 'none', "$max-md": { position: 'absolute', t: 0, r: 0, b: 0, l: 0, z: 'var(--z-dock)' }, $md: { display: 'flex' }, children: [_jsxs(XStack, { height: 40, items: "center", justify: "space-between", px: "$3.5", borderBottomWidth: 1, borderColor: "var(--white-06)", children: [_jsxs(XStack, { items: "center", gap: "$2", children: [_jsx(Activity, { size: 14, color: "$ink" }), _jsx(Text, { fontSize: 12, fontWeight: "600", color: "var(--neutral-200)", children: "Bioacoustic Telemetry" })] }), _jsx(View, { px: 6, py: 2, rounded: 4, bg: "var(--white-15)", children: _jsx(Text, { fontSize: 10, color: "$ink", fontFamily: "$mono", fontWeight: "600", children: "120 kHz" }) })] }), _jsxs(YStack, { flex: 1, overflowY: "auto", overflowX: "hidden", p: "$3.5", gap: "$4", children: [_jsxs(YStack, { gap: "$2", children: [_jsx(Text, { fontSize: 10, fontWeight: "700", color: "var(--neutral-500)", textTransform: "uppercase", children: "Active Emotion State" }), _jsxs(YStack, { p: "$3", rounded: 12, bg: "var(--neutral-900)", borderWidth: 1, borderColor: "var(--white-06)", gap: "$1", children: [_jsxs(XStack, { items: "center", gap: "$2", children: [_jsx(Text, { fontSize: 15, children: EMOTIONS[currentEmotion]?.emoji }), _jsx(Text, { fontSize: 13, fontWeight: "700", color: "var(--pure-white)", children: EMOTIONS[currentEmotion]?.name })] }), _jsx(Text, { fontSize: 11, color: "var(--neutral-400)", lineHeight: 16, children: EMOTIONS[currentEmotion]?.desc })] })] }), _jsxs(YStack, { gap: "$2", children: [_jsx(Text, { fontSize: 10, fontWeight: "700", color: "var(--neutral-500)", textTransform: "uppercase", children: "Emotion State Matrix" }), _jsx(YStack, { gap: "$1.5", children: Object.entries(EMOTIONS).map(([key, item]) => (_jsx(View, { cursor: "pointer", onPress: () => setCurrentEmotion(key), px: "$2.5", py: "$2", rounded: 8, borderWidth: 1, borderColor: currentEmotion === key ? 'var(--white-40)' : 'var(--white-06)', bg: currentEmotion === key ? 'var(--white-14)' : 'var(--neutral-900)', children: _jsxs(XStack, { items: "center", justify: "space-between", children: [_jsxs(XStack, { items: "center", gap: "$2", children: [_jsx(Text, { fontSize: 13, children: item.emoji }), _jsx(Text, { fontSize: 12, fontWeight: currentEmotion === key ? '700' : '400', color: currentEmotion === key ? 'var(--pure-white)' : 'var(--neutral-300)', children: item.name })] }), currentEmotion === key && (_jsx(View, { width: 6, height: 6, rounded: 3, bg: "var(--pure-white)" }))] }) }, key))) })] })] })] })] })] }));
145
- }
146
- // THE GATE IS NOT A ROOM'S TO REMEMBER. This room draws its own shell rather
147
- // than `Room`, which is where every other room picks the organization gate up —
148
- // so it was reachable, and usable, by an account that had never paid. Wrapping
149
- // the export means the route cannot mount the room without it.
150
- export function BelugaStudio() {
151
- return (_jsx(Orgs, { children: _jsx(BelugaStudioInner, {}) }));
152
- }
package/dist/Vibe.d.ts DELETED
@@ -1,2 +0,0 @@
1
- import React from 'react';
2
- export declare function VibeWorkspace(): React.JSX.Element;
package/dist/Vibe.js DELETED
@@ -1,189 +0,0 @@
1
- 'use client';
2
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
- /**
4
- * /vibe — three columns: the runs started here, a frame pointed at whatever you
5
- * are building, and the org's people.
6
- *
7
- * WHAT IS DRAWN WAS READ, OR IT IS NOT DRAWN. This room shipped as a stage set.
8
- * It opened holding three members nobody had invited — one of them an
9
- * individual, named in full at a live domain — all stamped "online" by a
10
- * literal and counted in the header as "3 online". Under them sat two finished
11
- * agent runs attributed to those people, priced at $0.0485 and $0.0192 over
12
- * 146.0s and 32.1s, and three chat messages carrying a poll whose 3-to-1 tally
13
- * nobody had voted in. The address bar named a host on another org's domain
14
- * while the frame below it loaded /ocean, a path this site does not serve.
15
- * Every one of those was typed into the file.
16
- *
17
- * The reads that answer are IAM's people, the platform's agents and models, and
18
- * POST /v1/agents/coding. The chat column went entirely: nothing it displayed
19
- * came from anywhere, and nothing typed into it ever left the browser — a room
20
- * that only ever talked to itself. The preview column kept its frame and lost
21
- * its claims: it now shows the URL in the bar, which is the one thing about it
22
- * that was ever true.
23
- */
24
- import { dev } from './lib/host.js';
25
- import { useEffect, useMemo, useRef, useState } from 'react';
26
- import { useRooms } from './host.js';
27
- import { ArrowUp, Globe, Monitor, RefreshCw, Smartphone, Sparkles, Tablet, UserPlus, Users, } from 'lucide-react';
28
- import { View, XStack, YStack, Text } from '@hanzo/gui';
29
- import { Button } from '@hanzo/ui';
30
- import { AiProvider, useAgents, useModels, usePeople } from '@hanzo/ai/react';
31
- import { ModelPicker } from '@hanzo/ui/models';
32
- import { parseModels } from '@hanzo/ui/models/catalog';
33
- import { useIam, useOrganizations } from '@hanzo/iam/react';
34
- import { hasSession } from './lib/session.js';
35
- import { useHydrated } from './lib/hydrated.js';
36
- import { ENSO, FREE, served, useAi } from './lib/ai.js';
37
- import { TopOrgSelector } from './OrgSwitcher.js';
38
- import { plainly, say } from './failure.js';
39
- import { invite } from './lib/home.js';
40
- import { startRun } from './lib/coding.js';
41
- import { Orgs } from './Orgs.js';
42
- const clock = () => new Date().toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });
43
- /** The one grey line, so every empty state in this room is written the same. */
44
- function Quiet({ children }) {
45
- return (_jsx(Text, { fontSize: 11, color: "$soft", lineHeight: 16, children: children }));
46
- }
47
- /**
48
- * The org's people and its agents.
49
- *
50
- * SPLIT OUT because `usePeople` needs an owner and a hook cannot be called
51
- * conditionally — the same reason Home keeps `Mates` apart from `Team`.
52
- *
53
- * NOBODY IS MARKED PRESENT. Nothing here subscribes to presence, so "online"
54
- * was a field set to the same string for everyone the moment the file loaded.
55
- * A directory can say who belongs to the org; it cannot say who is looking.
56
- */
57
- function Members({ owner }) {
58
- const { people, loading, error } = usePeople({ owner, service: false });
59
- const { agents, loading: counting, error: refused } = useAgents();
60
- const wrong = error ?? refused;
61
- if (wrong)
62
- return _jsx(Quiet, { children: say(wrong, 'your team') });
63
- const rows = [
64
- ...people.map((p) => ({
65
- key: p.id,
66
- name: p.displayName || p.name,
67
- detail: p.email || '',
68
- // A ROLE IS A CLAIM IAM MAKES OR NOBODY DOES.
69
- role: p.admin ? 'Admin' : '',
70
- mark: (p.displayName || p.name || '?').charAt(0).toUpperCase(),
71
- })),
72
- ...agents.map((a) => ({
73
- key: a.id,
74
- name: a.name,
75
- detail: a.model || '',
76
- role: 'Agent',
77
- mark: a.emoji || a.name.charAt(0).toUpperCase(),
78
- })),
79
- ];
80
- if (rows.length === 0)
81
- return _jsx(Quiet, { children: loading || counting ? 'Reading.' : 'Nobody in this org yet.' });
82
- return (_jsx(YStack, { gap: "$1.5", children: rows.map((r) => (_jsxs(XStack, { items: "center", justify: "space-between", p: "$2", rounded: 8, bg: "$panel", children: [_jsxs(XStack, { items: "center", gap: "$2.5", flex: 1, overflow: "hidden", children: [_jsx(View, { width: 24, height: 24, rounded: 12, bg: "$raised", items: "center", justify: "center", shrink: 0, children: _jsx(Text, { fontSize: 10, fontWeight: "700", color: "$ink", children: r.mark }) }), _jsxs(YStack, { flex: 1, overflow: "hidden", children: [_jsx(Text, { fontSize: 12, fontWeight: "600", color: "$ink", numberOfLines: 1, children: r.name }), r.detail ? (_jsx(Text, { fontSize: 10, color: "$soft", numberOfLines: 1, children: r.detail })) : null] })] }), r.role ? (_jsx(View, { px: 6, py: 2, rounded: 4, bg: "$hover", children: _jsx(Text, { fontSize: 10, color: "$soft", children: r.role }) })) : null] }, r.key))) }));
83
- }
84
- /**
85
- * The session is asked FIRST, because it is the state that never resolves — a
86
- * reader nobody will answer would sit on "Reading." until they closed the tab.
87
- *
88
- * `useHydrated` keeps the server's render and the first client one agreeing:
89
- * `useOrganizations` resolves synchronously in the browser and never on the
90
- * server, and React answers a text mismatch by throwing the tree away.
91
- */
92
- function Team() {
93
- const hydrated = useHydrated();
94
- const { isAuthenticated } = useIam();
95
- const signedIn = isAuthenticated || hasSession();
96
- const { currentOrgId } = useOrganizations() || {};
97
- if (!hydrated)
98
- return _jsx(Quiet, { children: "\u00A0" });
99
- if (!signedIn)
100
- return _jsx(Quiet, { children: "Sign in to see who is in this workspace." });
101
- if (!currentOrgId)
102
- return _jsx(Quiet, { children: "Reading." });
103
- return _jsx(Members, { owner: currentOrgId });
104
- }
105
- /**
106
- * THIS ROOM MOUNTS ITS OWN CLIENT.
107
- *
108
- * Every other room on this site arrives under `Room` → `Workspace`, which
109
- * supplies the `AiProvider` for the whole shell. `/vibe` is routed straight to
110
- * this component, so nothing above it does — and the moment anything here reads
111
- * the platform, `useAi` raises and the route answers 500. The provider prefers
112
- * the `client` prop over the one it builds itself, so a session that settles
113
- * after mount reaches the hooks below; signed out, its own anonymous client
114
- * stands in and the org-scoped reads simply answer nothing.
115
- *
116
- * The reading half is a separate component because a hook cannot run in the
117
- * component that mounts its provider.
118
- */
119
- function VibeWorkspaceInner() {
120
- const { client } = useAi();
121
- return (_jsx(AiProvider, { client: client ?? undefined, children: _jsx(Vibe, {}) }));
122
- }
123
- function Vibe() {
124
- const { Link } = useRooms();
125
- const { user } = useIam();
126
- // Every model the gateway lists, through the one picker.
127
- const { models: catalog } = useModels();
128
- const models = useMemo(() => parseModels(catalog), [catalog]);
129
- const [activeTabMobile, setActiveTabMobile] = useState('stream');
130
- // The frame shows what the bar says. It starts empty because a run publishes
131
- // no URL — the 202 names a session, not an address — so this room has nothing
132
- // to point at until somebody points it.
133
- const [previewUrl, setPreviewUrl] = useState('');
134
- const [previewDevice, setPreviewDevice] = useState('desktop');
135
- const [previewKey, setPreviewKey] = useState(0);
136
- const [agentInput, setAgentInput] = useState('');
137
- // The house name for the lane, resolved against what the gateway serves —
138
- // `served` falls to a model that answers rather than sending one nobody has.
139
- const [wanted, setWanted] = useState(user ? ENSO : FREE);
140
- const model = models.some((m) => m.id === wanted) ? wanted : served(models, wanted);
141
- const [busy, setBusy] = useState(false);
142
- const [turns, setTurns] = useState([]);
143
- const bottom = useRef(null);
144
- useEffect(() => {
145
- bottom.current?.scrollIntoView({ behavior: 'smooth' });
146
- }, [turns]);
147
- const who = user?.displayName || user?.name || user?.email || 'You';
148
- /**
149
- * SEND THE PROMPT, AND SAY WHAT CAME BACK.
150
- *
151
- * `repo` is no longer sent. It carried the room's name — which defaulted to
152
- * `ocean-genesis-room` and was editable free text — into a field the contract
153
- * defines as `owner/name` in the caller's own org, so a label typed in a
154
- * header reached a real write as a repository. Omitting it is what the
155
- * contract means by optional: the server picks the default.
156
- */
157
- const send = () => {
158
- const task = agentInput.trim();
159
- if (!task || busy)
160
- return;
161
- const id = `t${Date.now()}`;
162
- setAgentInput('');
163
- setBusy(true);
164
- setTurns((prev) => [...prev, { id, time: clock(), who, prompt: task, model }]);
165
- startRun({ prompt: task, model })
166
- .then((run) => setTurns((prev) => prev.map((t) => (t.id === id ? { ...t, run } : t))))
167
- .catch((e) => setTurns((prev) => prev.map((t) => (t.id === id ? { ...t, refused: plainly(e, 'this run') } : t))))
168
- .finally(() => setBusy(false));
169
- };
170
- return (_jsxs(YStack, { "data-vibe": true, flex: 1, minH: 0, height: "100vh", width: "100%", maxW: "100%", overflowX: "auto", overflowY: "hidden", bg: "$background", pb: 56, children: [_jsxs(XStack, { height: 52, items: "center", justify: "space-between", px: "$3.5", borderBottomWidth: 1, borderColor: "$borderColor", bg: "$background", z: 30, shrink: 0, children: [_jsxs(XStack, { items: "center", gap: "$2.5", children: [_jsx(TopOrgSelector, {}), _jsxs(XStack, { display: "none", $md: { display: 'flex' }, items: "center", gap: "$2", py: 4, px: 10, rounded: 20, bg: "$panel", borderWidth: 1, borderColor: "$borderColor", children: [_jsx(View, { width: 7, height: 7, rounded: 4, bg: busy ? 'var(--state-online)' : '$faint' }), _jsx(Text, { fontSize: 11, color: "$quiet", fontWeight: "500", children: busy ? 'Starting a run' : 'Idle' })] })] }), _jsxs(XStack, { items: "center", gap: "$2", children: [_jsx(XStack, { $md: { display: 'none' }, bg: "$panel", rounded: 8, p: 2, gap: 2, borderWidth: 1, borderColor: "$borderColor", children: ['stream', 'preview', 'team'].map((tab) => (_jsx(Button, { size: "sm", px: "$2", variant: activeTabMobile === tab ? 'secondary' : 'ghost', color: activeTabMobile === tab ? '$ink' : '$soft', "aria-pressed": activeTabMobile === tab, onClick: () => setActiveTabMobile(tab), children: tab === 'stream' ? 'Stream' : tab === 'preview' ? 'Preview' : 'Team' }, tab))) }), _jsx(Button, { asChild: true, variant: "primary", size: "sm", children: _jsxs(Link, { href: invite(), children: [_jsx(UserPlus, { size: 13 }), "Invite"] }) })] })] }), _jsxs(View, { flex: 1, minH: 0, width: "100%", height: "100%", overflow: "hidden", display: "grid", gridTemplateRows: "100%", gridTemplateColumns: "100%", $md: { gridTemplateColumns: 'minmax(320px, 380px) minmax(0, 1fr)' }, $lg: { gridTemplateColumns: 'minmax(340px, 400px) minmax(360px, 1fr) minmax(300px, 340px)' }, $xl: { gridTemplateColumns: 'minmax(380px, 440px) minmax(480px, 1fr) minmax(340px, 380px)' }, children: [_jsxs(YStack, { height: "100%", borderRightWidth: 1, borderColor: "$borderColor", bg: "$background", minW: 0, display: activeTabMobile === 'stream' ? 'flex' : 'none', $md: { display: 'flex' }, children: [_jsxs(XStack, { height: 42, items: "center", gap: "$2", px: "$3.5", borderBottomWidth: 1, borderColor: "$borderColor", bg: "$background", children: [_jsx(Sparkles, { size: 14, color: "var(--foreground)" }), _jsx(Text, { fontSize: 12, fontWeight: "600", color: "$ink", children: "Runs" }), turns.length > 0 && (_jsx(View, { px: 6, py: 2, rounded: 4, bg: "$hover", children: _jsx(Text, { fontSize: 10, color: "$soft", children: turns.length }) }))] }), _jsxs(YStack, { flex: 1, overflowY: "auto", overflowX: "hidden", p: "$3.5", gap: "$4", children: [turns.length === 0 ? (_jsx(Quiet, { children: "Nothing has been sent from this room yet. Describe a task below and the run it opens appears here." })) : null, turns.map((turn) => (_jsxs(YStack, { gap: "$2.5", children: [_jsxs(XStack, { gap: "$2.5", children: [_jsx(View, { width: 24, height: 24, rounded: 12, bg: "$raised", items: "center", justify: "center", shrink: 0, children: _jsx(Text, { fontSize: 10, fontWeight: "700", color: "$ink", children: turn.who.charAt(0).toUpperCase() }) }), _jsxs(YStack, { flex: 1, gap: "$1", children: [_jsxs(XStack, { items: "center", gap: "$2", children: [_jsx(Text, { fontSize: 12, fontWeight: "700", color: "$ink", children: turn.who }), _jsx(Text, { fontSize: 11, color: "$soft", children: turn.time }), _jsx(View, { px: 5, py: 1, rounded: 4, bg: "$hover", children: _jsx(Text, { fontSize: 10, color: "$soft", fontFamily: "$mono", children: turn.model }) })] }), _jsx(Text, { fontSize: 12, color: "$quiet", lineHeight: 18, children: turn.prompt })] })] }), _jsx(YStack, { ml: "$4", gap: "$1.5", children: turn.refused ? (_jsx(Text, { fontSize: 11, color: "$ink", lineHeight: 16, children: turn.refused })) : !turn.run ? (_jsx(Quiet, { children: "Starting." })) : (_jsxs(YStack, { p: "$2", rounded: 8, bg: "$panel", borderWidth: 1, borderColor: "$borderColor", gap: "$1", children: [_jsx(Text, { fontSize: 11, color: "$quiet", fontFamily: "$mono", children: turn.run.sessionId }), _jsxs(Text, { fontSize: 11, color: "$soft", children: [turn.run.routed
171
- ? `Running on ${turn.run.targetId}`
172
- : 'Running in a sandbox', turn.run.branch ? ` · ${turn.run.branch}` : ''] }), _jsx(Text, { render: _jsx(Link, { href: dev(turn.run.sessionId) }), color: "$ink", fontSize: "$1", children: "Watch it work" })] })) })] }, turn.id))), _jsx("div", { ref: bottom })] }), _jsxs(YStack, { p: "$3", borderTopWidth: 1, borderColor: "$borderColor", bg: "$background", gap: "$2", children: [_jsxs(XStack, { items: "center", justify: "space-between", gap: "$2", children: [_jsx(ModelPicker, { models: models, scope: "chat", value: model, onChange: setWanted, size: "sm", quiet: true }), _jsx(Text, { fontSize: 10, color: "$faint", children: "Press Enter to send" })] }), _jsxs(YStack, { bg: "$panel", borderWidth: 1, borderColor: "var(--border-control)", rounded: 12, overflow: "hidden", children: [_jsx(Text, { render: _jsx("textarea", { value: agentInput, onChange: (e) => setAgentInput(e.target.value), onKeyDown: (e) => {
173
- if (e.key === 'Enter' && !e.shiftKey) {
174
- e.preventDefault();
175
- send();
176
- }
177
- }, placeholder: "Describe a task and an agent starts on it\u2026", rows: 2, disabled: busy }), "$platform-web": { resize: 'none' }, width: "100%", bg: "transparent", px: 12, py: 10, color: "$ink", fontSize: "$2", outlineStyle: "none", borderWidth: 0 }), _jsx(XStack, { items: "center", justify: "flex-end", px: "$2.5", pb: "$2", children: _jsxs(Button, { variant: "primary", size: "sm", onClick: send, disabled: !agentInput.trim() || busy, children: ["Send", _jsx(ArrowUp, { size: 13 })] }) })] })] })] }), _jsxs(YStack, { height: "100%", borderRightWidth: 1, borderColor: "$borderColor", bg: "$background", minW: 0, display: activeTabMobile === 'preview' ? 'flex' : 'none', $md: { display: 'flex' }, children: [_jsxs(XStack, { height: 42, items: "center", justify: "space-between", gap: "$2", px: "$3", borderBottomWidth: 1, borderColor: "$borderColor", bg: "$background", children: [_jsxs(XStack, { items: "center", gap: "$2", flex: 1, maxW: 420, bg: "$panel", rounded: 6, px: "$2.5", py: 3, borderWidth: 1, borderColor: "var(--border-control)", children: [_jsx(Globe, { size: 13, color: "var(--muted-foreground)" }), _jsx(Text, { render: _jsx("input", { type: "text", value: previewUrl, onChange: (e) => setPreviewUrl(e.target.value), placeholder: "https://\u2026" }), flex: 1, bg: "transparent", borderWidth: 0, color: "$ink", fontSize: "$1", fontFamily: "$mono", outlineStyle: "none" })] }), _jsxs(XStack, { items: "center", gap: "$2", children: [_jsx(XStack, { items: "center", gap: 2, bg: "$panel", rounded: 6, p: 2, borderWidth: 1, borderColor: "$borderColor", children: [
178
- ['desktop', 'Desktop', Monitor],
179
- ['tablet', 'Tablet', Tablet],
180
- ['mobile', 'Mobile', Smartphone],
181
- ].map(([device, label, Glyph]) => (_jsx(Button, { variant: previewDevice === device ? 'secondary' : 'ghost', size: "icon-sm", "aria-label": label, title: label, "aria-pressed": previewDevice === device, onClick: () => setPreviewDevice(device), children: _jsx(Glyph, { size: 12, color: previewDevice === device ? 'var(--foreground)' : 'var(--muted-foreground)' }) }, device))) }), _jsx(Button, { variant: "ghost", size: "icon-sm", "aria-label": "Reload preview", title: "Reload preview", onClick: () => setPreviewKey((k) => k + 1), children: _jsx(RefreshCw, { size: 12, color: "var(--muted-foreground)" }) })] })] }), _jsx(YStack, { flex: 1, items: "center", justify: "center", bg: "$background", p: "$2", children: previewUrl.trim() === '' ? (_jsx(Quiet, { children: "Nothing to show. Put the address of what you are building in the bar." })) : (_jsx(View, { width: previewDevice === 'desktop' ? '100%' : previewDevice === 'tablet' ? 768 : 375, maxW: "100%", height: "100%", bg: "$background", rounded: previewDevice === 'desktop' ? 0 : 8, overflow: "hidden", borderWidth: previewDevice === 'desktop' ? 0 : 1, borderColor: "var(--border-control)", boxShadow: "var(--shadow-lg)", children: _jsx(View, { render: _jsx("iframe", { src: previewUrl, sandbox: "allow-scripts allow-forms allow-popups allow-same-origin", title: "Preview" }), width: "100%", height: "100%", borderWidth: 0 }) }, previewKey)) })] }), _jsxs(YStack, { height: "100%", bg: "$background", minW: 0, display: activeTabMobile === 'team' ? 'flex' : 'none', $md: { display: 'none' }, $lg: { display: 'flex' }, children: [_jsxs(XStack, { height: 42, items: "center", gap: "$2", px: "$3", borderBottomWidth: 1, borderColor: "$borderColor", bg: "$background", children: [_jsx(Users, { size: 14, color: "var(--foreground)" }), _jsx(Text, { fontSize: 12, fontWeight: "600", color: "$ink", children: "People" })] }), _jsx(YStack, { flex: 1, overflowY: "auto", overflowX: "hidden", p: "$3", gap: "$3", children: _jsx(Team, {}) })] })] })] }));
182
- }
183
- // THE GATE IS NOT A ROOM'S TO REMEMBER. This room draws its own shell rather
184
- // than `Room`, which is where every other room picks the organization gate up —
185
- // so it was reachable, and usable, by an account that had never paid. Wrapping
186
- // the export means the route cannot mount the room without it.
187
- export function VibeWorkspace() {
188
- return (_jsx(Orgs, { children: _jsx(VibeWorkspaceInner, {}) }));
189
- }