@hanzo/rooms 0.1.1 → 0.1.3

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/Beluga.js CHANGED
@@ -141,7 +141,7 @@ function BelugaStudioInner() {
141
141
  setMobileDrawer('none');
142
142
  }
143
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, overflow: "scroll", 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, overflow: "scroll", 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))) })] })] })] })] })] }));
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
145
  }
146
146
  // THE GATE IS NOT A ROOM'S TO REMEMBER. This room draws its own shell rather
147
147
  // than `Room`, which is where every other room picks the organization gate up —
package/dist/Board.js CHANGED
@@ -179,7 +179,7 @@ function Detail({ card, people, agents, onClose, onSaved, }) {
179
179
  setBody(written);
180
180
  void save({ description: written });
181
181
  };
182
- return (_jsxs(YStack, { position: "absolute", t: 0, r: 0, b: 0, width: 560, maxW: "100%", bg: "$panel", borderLeftWidth: 1, borderColor: "$borderColor", z: "var(--z-sheet)", children: [_jsxs(XStack, { items: "center", gap: "$2", px: "$4", py: "$3", borderBottomWidth: 1, borderColor: "$borderColor", children: [_jsx(Text, { fontSize: "$1", color: "$soft", flex: 1, numberOfLines: 1, children: card.identifier }), _jsx(Box, { render: "button", onClick: onClose, p: "$1", rounded: "$2", "aria-label": "Close", children: _jsx(X, { size: 16, "aria-hidden": true }) })] }), _jsxs(YStack, { flex: 1, minH: 0, overflow: "scroll", p: "$4", gap: "$4", children: [_jsxs(XStack, { items: "center", gap: "$2", children: [_jsx(Box, { render: "button", onClick: () => void save({ status: finished ? 'todo' : 'done' }), "aria-label": finished ? 'Reopen this card' : 'Mark this card done', "aria-pressed": finished, width: 22, height: 22, shrink: 0, rounded: 9999, borderWidth: finished ? 0 : 1.5, borderColor: "$soft", bg: finished ? '$ink' : 'transparent', items: "center", justify: "center", hoverStyle: { borderColor: '$ink' }, children: finished ? _jsx(Check, { size: 13, color: "var(--background)", "aria-hidden": true }) : null }), _jsx(Text, { render: _jsx("input", { value: title, onChange: (e) => setTitle(e.target.value), onBlur: () => title.trim() && title !== card.title && void save({ title: title.trim() }), "aria-label": "Card title" }), flex: 1, minW: 0, bg: "transparent", borderWidth: 0, outlineStyle: "none", color: "inherit", fontSize: "$7", fontWeight: "600", textDecorationLine: finished ? 'line-through' : 'none' })] }), _jsxs(XStack, { gap: "$1.5", flexWrap: "wrap", children: [_jsx(Act, { icon: Tag, label: "Labels", on: panel === 'labels', onPress: () => setPanel(panel === 'labels' ? null : 'labels') }), _jsx(Act, { icon: Calendar, label: "Dates", on: panel === 'dates', onPress: () => setPanel(panel === 'dates' ? null : 'dates') }), _jsx(Act, { icon: UserPlus, label: "Members", on: panel === 'members', onPress: () => setPanel(panel === 'members' ? null : 'members') })] }), panel === 'labels' ? (_jsxs(YStack, { gap: "$2", p: "$3", rounded: "$3", borderWidth: 1, borderColor: "$borderColor", children: [_jsx(Text, { fontSize: "$2", fontWeight: "600", color: "$ink", children: "Labels" }), card.labels.length > 0 ? (_jsx(XStack, { gap: "$1", flexWrap: "wrap", children: card.labels.map((l) => (_jsx(Label, { name: l }, l))) })) : (_jsx(Text, { fontSize: "$1", color: "$soft", children: "None yet." })), _jsx(Text, { fontSize: "$1", color: "$soft", children: "A label is a word on the forge issue, and its colour comes from the word \u2014 so it reads the same here and there. Labels are added on the forge; this board does not write them yet." })] })) : null, panel === 'dates' ? (_jsxs(YStack, { gap: "$2", p: "$3", rounded: "$3", borderWidth: 1, borderColor: "$borderColor", children: [_jsx(Text, { fontSize: "$2", fontWeight: "600", color: "$ink", children: "Dates" }), _jsxs(XStack, { gap: "$3", flexWrap: "wrap", children: [_jsxs(YStack, { gap: "$1", children: [_jsx(Text, { fontSize: "$1", color: "$soft", children: "Start" }), _jsx(Text, { render: _jsx("input", { type: "date", value: asInput(card.startAt), readOnly: true }), ...DATE })] }), _jsxs(YStack, { gap: "$1", children: [_jsx(Text, { fontSize: "$1", color: "$soft", children: "Due" }), _jsx(Text, { render: _jsx("input", { type: "date", value: asInput(card.dueAt), readOnly: true }), ...DATE })] })] }), _jsx(Text, { fontSize: "$1", color: "$soft", children: "A card's dates come from its forge milestone, which is why they read here and cannot yet be set here." })] })) : null, panel === 'members' ? (_jsxs(YStack, { gap: "$2", p: "$3", rounded: "$3", borderWidth: 1, borderColor: "$borderColor", children: [_jsx(Text, { fontSize: "$2", fontWeight: "600", color: "$ink", children: "Members" }), people.length === 0 ? (_jsx(Text, { fontSize: "$1", color: "$soft", children: "Nobody to assign yet." })) : (people.map((p) => (_jsx(Box, { render: "button", onClick: () => void save({ assignee: card.assignee === p.name ? '' : p.name }), width: "100%", p: "$2", rounded: "$2", hoverStyle: { bg: '$hover' }, children: _jsxs(XStack, { items: "center", gap: "$2", children: [_jsx(Face, { src: p.avatar, emoji: p.emoji, name: p.label, size: 22 }), _jsx(Text, { fontSize: "$2", color: "$ink", flex: 1, numberOfLines: 1, children: p.label }), p.agent ? (_jsx(Text, { fontSize: "$1", color: "$soft", children: "agent" })) : null, card.assignee === p.name ? _jsx(Check, { size: 14, "aria-hidden": true }) : null] }) }, p.id)))), _jsx(Box, { render: "button", onClick: () => {
182
+ return (_jsxs(YStack, { position: "absolute", t: 0, r: 0, b: 0, width: 560, maxW: "100%", bg: "$panel", borderLeftWidth: 1, borderColor: "$borderColor", z: "var(--z-sheet)", children: [_jsxs(XStack, { items: "center", gap: "$2", px: "$4", py: "$3", borderBottomWidth: 1, borderColor: "$borderColor", children: [_jsx(Text, { fontSize: "$1", color: "$soft", flex: 1, numberOfLines: 1, children: card.identifier }), _jsx(Box, { render: "button", onClick: onClose, p: "$1", rounded: "$2", "aria-label": "Close", children: _jsx(X, { size: 16, "aria-hidden": true }) })] }), _jsxs(YStack, { flex: 1, minH: 0, overflowY: "auto", overflowX: "hidden", p: "$4", gap: "$4", children: [_jsxs(XStack, { items: "center", gap: "$2", children: [_jsx(Box, { render: "button", onClick: () => void save({ status: finished ? 'todo' : 'done' }), "aria-label": finished ? 'Reopen this card' : 'Mark this card done', "aria-pressed": finished, width: 22, height: 22, shrink: 0, rounded: 9999, borderWidth: finished ? 0 : 1.5, borderColor: "$soft", bg: finished ? '$ink' : 'transparent', items: "center", justify: "center", hoverStyle: { borderColor: '$ink' }, children: finished ? _jsx(Check, { size: 13, color: "var(--background)", "aria-hidden": true }) : null }), _jsx(Text, { render: _jsx("input", { value: title, onChange: (e) => setTitle(e.target.value), onBlur: () => title.trim() && title !== card.title && void save({ title: title.trim() }), "aria-label": "Card title" }), flex: 1, minW: 0, bg: "transparent", borderWidth: 0, outlineStyle: "none", color: "inherit", fontSize: "$7", fontWeight: "600", textDecorationLine: finished ? 'line-through' : 'none' })] }), _jsxs(XStack, { gap: "$1.5", flexWrap: "wrap", children: [_jsx(Act, { icon: Tag, label: "Labels", on: panel === 'labels', onPress: () => setPanel(panel === 'labels' ? null : 'labels') }), _jsx(Act, { icon: Calendar, label: "Dates", on: panel === 'dates', onPress: () => setPanel(panel === 'dates' ? null : 'dates') }), _jsx(Act, { icon: UserPlus, label: "Members", on: panel === 'members', onPress: () => setPanel(panel === 'members' ? null : 'members') })] }), panel === 'labels' ? (_jsxs(YStack, { gap: "$2", p: "$3", rounded: "$3", borderWidth: 1, borderColor: "$borderColor", children: [_jsx(Text, { fontSize: "$2", fontWeight: "600", color: "$ink", children: "Labels" }), card.labels.length > 0 ? (_jsx(XStack, { gap: "$1", flexWrap: "wrap", children: card.labels.map((l) => (_jsx(Label, { name: l }, l))) })) : (_jsx(Text, { fontSize: "$1", color: "$soft", children: "None yet." })), _jsx(Text, { fontSize: "$1", color: "$soft", children: "A label is a word on the forge issue, and its colour comes from the word \u2014 so it reads the same here and there. Labels are added on the forge; this board does not write them yet." })] })) : null, panel === 'dates' ? (_jsxs(YStack, { gap: "$2", p: "$3", rounded: "$3", borderWidth: 1, borderColor: "$borderColor", children: [_jsx(Text, { fontSize: "$2", fontWeight: "600", color: "$ink", children: "Dates" }), _jsxs(XStack, { gap: "$3", flexWrap: "wrap", children: [_jsxs(YStack, { gap: "$1", children: [_jsx(Text, { fontSize: "$1", color: "$soft", children: "Start" }), _jsx(Text, { render: _jsx("input", { type: "date", value: asInput(card.startAt), readOnly: true }), ...DATE })] }), _jsxs(YStack, { gap: "$1", children: [_jsx(Text, { fontSize: "$1", color: "$soft", children: "Due" }), _jsx(Text, { render: _jsx("input", { type: "date", value: asInput(card.dueAt), readOnly: true }), ...DATE })] })] }), _jsx(Text, { fontSize: "$1", color: "$soft", children: "A card's dates come from its forge milestone, which is why they read here and cannot yet be set here." })] })) : null, panel === 'members' ? (_jsxs(YStack, { gap: "$2", p: "$3", rounded: "$3", borderWidth: 1, borderColor: "$borderColor", children: [_jsx(Text, { fontSize: "$2", fontWeight: "600", color: "$ink", children: "Members" }), people.length === 0 ? (_jsx(Text, { fontSize: "$1", color: "$soft", children: "Nobody to assign yet." })) : (people.map((p) => (_jsx(Box, { render: "button", onClick: () => void save({ assignee: card.assignee === p.name ? '' : p.name }), width: "100%", p: "$2", rounded: "$2", hoverStyle: { bg: '$hover' }, children: _jsxs(XStack, { items: "center", gap: "$2", children: [_jsx(Face, { src: p.avatar, emoji: p.emoji, name: p.label, size: 22 }), _jsx(Text, { fontSize: "$2", color: "$ink", flex: 1, numberOfLines: 1, children: p.label }), p.agent ? (_jsx(Text, { fontSize: "$1", color: "$soft", children: "agent" })) : null, card.assignee === p.name ? _jsx(Check, { size: 14, "aria-hidden": true }) : null] }) }, p.id)))), _jsx(Box, { render: "button", onClick: () => {
183
183
  if (!client || busy)
184
184
  return;
185
185
  setBusy(true);
@@ -242,7 +242,7 @@ function Planner({ cards, onOpen }) {
242
242
  }, [offset]);
243
243
  const start = Math.floor(day.getTime() / 1000);
244
244
  const due = cards.filter((c) => c.dueAt && c.dueAt >= start && c.dueAt < start + DAY);
245
- return (_jsxs(YStack, { flex: 1, minW: 0, minH: 0, children: [_jsxs(XStack, { items: "center", gap: "$2", px: "$3", py: "$3", borderBottomWidth: 1, borderColor: "$borderColor", children: [_jsx(Calendar, { size: 14, "aria-hidden": true }), _jsx(Text, { fontSize: "$2", fontWeight: "600", color: "$ink", flex: 1, children: day.toLocaleDateString(undefined, { month: 'long' }) }), _jsx(Box, { render: "button", onClick: () => setOffset((n) => n - 1), p: "$1", rounded: "$2", "aria-label": "Previous day", children: _jsx(ChevronLeft, { size: 14, "aria-hidden": true }) }), _jsx(Box, { render: "button", onClick: () => setOffset(0), px: "$2", py: "$1", rounded: "$2", borderWidth: 1, borderColor: "$borderColor", children: _jsx(Text, { fontSize: "$1", color: "$ink", children: "Today" }) }), _jsx(Box, { render: "button", onClick: () => setOffset((n) => n + 1), p: "$1", rounded: "$2", "aria-label": "Next day", children: _jsx(ChevronRight, { size: 14, "aria-hidden": true }) })] }), _jsxs(YStack, { items: "center", py: "$2", gap: "$1", children: [_jsx(Text, { fontSize: "$1", color: "$soft", children: dayName(day) }), _jsx(Text, { fontSize: "$4", fontWeight: "600", color: "$ink", children: day.getDate() })] }), _jsxs(YStack, { flex: 1, minH: 0, overflow: "scroll", children: [due.length > 0 ? (_jsxs(YStack, { p: "$3", gap: "$2", children: [_jsx(Text, { fontSize: "$1", color: "$soft", children: "Due today" }), due.map((c) => (_jsx(Box, { render: "button", onClick: () => onOpen(c), width: "100%", p: "$2.5", rounded: "$3", borderWidth: 1, borderColor: "$borderColor", bg: "$raised", hoverStyle: { borderColor: '$ink' }, children: _jsx(Text, { fontSize: "$2", color: "$ink", numberOfLines: 2, text: "left", children: c.title }) }, c.id)))] })) : null, Array.from({ length: 12 }, (_, i) => i + 8).map((hour) => (_jsx(XStack, { height: 56, borderTopWidth: 1, borderColor: "$borderColor", children: _jsx(YStack, { width: 54, pt: "$1", pl: "$3", children: _jsxs(Text, { fontSize: "$1", color: "$soft", children: [hour % 12 === 0 ? 12 : hour % 12, " ", hour < 12 ? 'am' : 'pm'] }) }) }, hour)))] })] }));
245
+ return (_jsxs(YStack, { flex: 1, minW: 0, minH: 0, children: [_jsxs(XStack, { items: "center", gap: "$2", px: "$3", py: "$3", borderBottomWidth: 1, borderColor: "$borderColor", children: [_jsx(Calendar, { size: 14, "aria-hidden": true }), _jsx(Text, { fontSize: "$2", fontWeight: "600", color: "$ink", flex: 1, children: day.toLocaleDateString(undefined, { month: 'long' }) }), _jsx(Box, { render: "button", onClick: () => setOffset((n) => n - 1), p: "$1", rounded: "$2", "aria-label": "Previous day", children: _jsx(ChevronLeft, { size: 14, "aria-hidden": true }) }), _jsx(Box, { render: "button", onClick: () => setOffset(0), px: "$2", py: "$1", rounded: "$2", borderWidth: 1, borderColor: "$borderColor", children: _jsx(Text, { fontSize: "$1", color: "$ink", children: "Today" }) }), _jsx(Box, { render: "button", onClick: () => setOffset((n) => n + 1), p: "$1", rounded: "$2", "aria-label": "Next day", children: _jsx(ChevronRight, { size: 14, "aria-hidden": true }) })] }), _jsxs(YStack, { items: "center", py: "$2", gap: "$1", children: [_jsx(Text, { fontSize: "$1", color: "$soft", children: dayName(day) }), _jsx(Text, { fontSize: "$4", fontWeight: "600", color: "$ink", children: day.getDate() })] }), _jsxs(YStack, { flex: 1, minH: 0, overflowY: "auto", overflowX: "hidden", children: [due.length > 0 ? (_jsxs(YStack, { p: "$3", gap: "$2", children: [_jsx(Text, { fontSize: "$1", color: "$soft", children: "Due today" }), due.map((c) => (_jsx(Box, { render: "button", onClick: () => onOpen(c), width: "100%", p: "$2.5", rounded: "$3", borderWidth: 1, borderColor: "$borderColor", bg: "$raised", hoverStyle: { borderColor: '$ink' }, children: _jsx(Text, { fontSize: "$2", color: "$ink", numberOfLines: 2, text: "left", children: c.title }) }, c.id)))] })) : null, Array.from({ length: 12 }, (_, i) => i + 8).map((hour) => (_jsx(XStack, { height: 56, borderTopWidth: 1, borderColor: "$borderColor", children: _jsx(YStack, { width: 54, pt: "$1", pl: "$3", children: _jsxs(Text, { fontSize: "$1", color: "$soft", children: [hour % 12 === 0 ? 12 : hour % 12, " ", hour < 12 ? 'am' : 'pm'] }) }) }, hour)))] })] }));
246
246
  }
247
247
  // ── the board ───────────────────────────────────────────────────────────────
248
248
  function Column({ column, cards, people, onDrop, onOpen, onAdd, loading, }) {
@@ -258,7 +258,7 @@ function Column({ column, cards, people, onDrop, onOpen, onAdd, loading, }) {
258
258
  const ref = e.dataTransfer.getData('text/plain');
259
259
  if (ref)
260
260
  onDrop(ref, column.id);
261
- }, children: _jsxs(YStack, { width: 300, gap: "$2", p: "$2", rounded: "$3", borderWidth: 1, borderColor: over ? '$ink' : '$borderColor', bg: "$hover", maxH: "100%", children: [_jsxs(XStack, { items: "baseline", gap: "$2", px: "$2", pt: "$1", children: [_jsx(Text, { fontSize: "$2", fontWeight: "600", color: "$ink", flex: 1, children: column.label }), _jsx(Text, { fontSize: "$1", color: "$soft", children: loading ? '' : cards.length })] }), _jsx(YStack, { gap: "$2", overflow: "scroll", shrink: 1, children: cards.map((c) => (_jsx(Tile, { card: c, who: people.find((p) => p.name === c.assignee), onOpen: () => onOpen(c) }, c.id))) }), adding ? (_jsx(YStack, { gap: "$2", children: _jsx(Text, { render: _jsx("textarea", { value: title, autoFocus: true, onChange: (e) => setTitle(e.target.value), onKeyDown: (e) => {
261
+ }, children: _jsxs(YStack, { width: 300, gap: "$2", p: "$2", rounded: "$3", borderWidth: 1, borderColor: over ? '$ink' : '$borderColor', bg: "$hover", maxH: "100%", children: [_jsxs(XStack, { items: "baseline", gap: "$2", px: "$2", pt: "$1", children: [_jsx(Text, { fontSize: "$2", fontWeight: "600", color: "$ink", flex: 1, children: column.label }), _jsx(Text, { fontSize: "$1", color: "$soft", children: loading ? '' : cards.length })] }), _jsx(YStack, { gap: "$2", overflowY: "auto", overflowX: "hidden", shrink: 1, children: cards.map((c) => (_jsx(Tile, { card: c, who: people.find((p) => p.name === c.assignee), onOpen: () => onOpen(c) }, c.id))) }), adding ? (_jsx(YStack, { gap: "$2", children: _jsx(Text, { render: _jsx("textarea", { value: title, autoFocus: true, onChange: (e) => setTitle(e.target.value), onKeyDown: (e) => {
262
262
  if (e.key === 'Escape') {
263
263
  setAdding(false);
264
264
  setTitle('');
@@ -307,7 +307,7 @@ function Filter({ want, labels, onWant, onClose, }) {
307
307
  set({ labels: held.includes(l) ? held.filter((x) => x !== l) : [...held, l] });
308
308
  };
309
309
  const Head = ({ children }) => (_jsx(Text, { fontSize: "$1", color: "$soft", px: "$2", pt: "$2", children: children }));
310
- return (_jsxs(YStack, { position: "absolute", t: 52, r: 12, width: 300, maxH: 560, overflow: "scroll", p: "$2", gap: "$0.5", rounded: "$4", borderWidth: 1, borderColor: "$borderColor", bg: "$panel", z: "var(--z-dock)", children: [_jsxs(XStack, { items: "center", px: "$2", py: "$1", children: [_jsx(Text, { fontSize: "$2", fontWeight: "600", color: "$ink", flex: 1, children: "Filter" }), _jsx(Box, { render: "button", onClick: onClose, p: "$1", rounded: "$2", "aria-label": "Close filter", children: _jsx(X, { size: 14, "aria-hidden": true }) })] }), _jsx(Tick, { on: want.holder === 'none', label: "Nobody has it", onPress: () => set({ holder: want.holder === 'none' ? undefined : 'none' }) }), _jsx(Tick, { on: want.holder === 'me', label: "Mine", onPress: () => set({ holder: want.holder === 'me' ? undefined : 'me' }) }), _jsx(Head, { children: "Who did the work" }), _jsx(Tick, { on: want.by === 'person', label: "A person", onPress: () => set({ by: want.by === 'person' ? undefined : 'person' }) }), _jsx(Tick, { on: want.by === 'agent', label: "An agent", onPress: () => set({ by: want.by === 'agent' ? undefined : 'agent' }) }), _jsx(Head, { children: "Card status" }), _jsx(Tick, { on: want.state === 'done', label: "Finished", onPress: () => set({ state: want.state === 'done' ? undefined : 'done' }) }), _jsx(Tick, { on: want.state === 'open', label: "Still open", onPress: () => set({ state: want.state === 'open' ? undefined : 'open' }) }), _jsx(Head, { children: "Due date" }), [['none', 'No dates'], ['late', 'Overdue'], ['day', 'Due in the next day'], ['week', 'Due in the next week'], ['month', 'Due in the next month']].map(([id, label]) => (_jsx(Tick, { on: want.due === id, label: label, onPress: () => set({ due: want.due === id ? undefined : id }) }, id))), labels.length > 0 ? (_jsxs(_Fragment, { children: [_jsx(Head, { children: "Labels" }), labels.map((l) => (_jsx(Tick, { on: (want.labels ?? []).includes(l), label: _jsx(Label, { name: l }), onPress: () => flipLabel(l) }, l)))] })) : null, _jsx(Head, { children: "Activity" }), [[7, 'Moved in the last week'], [14, 'Moved in the last two weeks'], [28, 'Moved in the last four weeks']].map(([days, label]) => (_jsx(Tick, { on: want.active === days, label: label, onPress: () => set({ active: want.active === days ? undefined : days }) }, days))), _jsx(Box, { render: "button", onClick: () => onWant({}), width: "100%", mt: "$2", p: "$2", rounded: "$2", borderWidth: 1, borderColor: "$borderColor", hoverStyle: { bg: '$hover' }, children: _jsx(Text, { fontSize: "$1", color: "$ink", children: "Clear" }) })] }));
310
+ return (_jsxs(YStack, { position: "absolute", t: 52, r: 12, width: 300, maxH: 560, overflowY: "auto", overflowX: "hidden", p: "$2", gap: "$0.5", rounded: "$4", borderWidth: 1, borderColor: "$borderColor", bg: "$panel", z: "var(--z-dock)", children: [_jsxs(XStack, { items: "center", px: "$2", py: "$1", children: [_jsx(Text, { fontSize: "$2", fontWeight: "600", color: "$ink", flex: 1, children: "Filter" }), _jsx(Box, { render: "button", onClick: onClose, p: "$1", rounded: "$2", "aria-label": "Close filter", children: _jsx(X, { size: 14, "aria-hidden": true }) })] }), _jsx(Tick, { on: want.holder === 'none', label: "Nobody has it", onPress: () => set({ holder: want.holder === 'none' ? undefined : 'none' }) }), _jsx(Tick, { on: want.holder === 'me', label: "Mine", onPress: () => set({ holder: want.holder === 'me' ? undefined : 'me' }) }), _jsx(Head, { children: "Who did the work" }), _jsx(Tick, { on: want.by === 'person', label: "A person", onPress: () => set({ by: want.by === 'person' ? undefined : 'person' }) }), _jsx(Tick, { on: want.by === 'agent', label: "An agent", onPress: () => set({ by: want.by === 'agent' ? undefined : 'agent' }) }), _jsx(Head, { children: "Card status" }), _jsx(Tick, { on: want.state === 'done', label: "Finished", onPress: () => set({ state: want.state === 'done' ? undefined : 'done' }) }), _jsx(Tick, { on: want.state === 'open', label: "Still open", onPress: () => set({ state: want.state === 'open' ? undefined : 'open' }) }), _jsx(Head, { children: "Due date" }), [['none', 'No dates'], ['late', 'Overdue'], ['day', 'Due in the next day'], ['week', 'Due in the next week'], ['month', 'Due in the next month']].map(([id, label]) => (_jsx(Tick, { on: want.due === id, label: label, onPress: () => set({ due: want.due === id ? undefined : id }) }, id))), labels.length > 0 ? (_jsxs(_Fragment, { children: [_jsx(Head, { children: "Labels" }), labels.map((l) => (_jsx(Tick, { on: (want.labels ?? []).includes(l), label: _jsx(Label, { name: l }), onPress: () => flipLabel(l) }, l)))] })) : null, _jsx(Head, { children: "Activity" }), [[7, 'Moved in the last week'], [14, 'Moved in the last two weeks'], [28, 'Moved in the last four weeks']].map(([days, label]) => (_jsx(Tick, { on: want.active === days, label: label, onPress: () => set({ active: want.active === days ? undefined : days }) }, days))), _jsx(Box, { render: "button", onClick: () => onWant({}), width: "100%", mt: "$2", p: "$2", rounded: "$2", borderWidth: 1, borderColor: "$borderColor", hoverStyle: { bg: '$hover' }, children: _jsx(Text, { fontSize: "$1", color: "$ink", children: "Clear" }) })] }));
311
311
  }
312
312
  // ── the room ────────────────────────────────────────────────────────────────
313
313
  /** Said when a write has no board to write to. It is the same event whichever
@@ -432,7 +432,7 @@ function Cards() {
432
432
  }, [client, key, keys]);
433
433
  return (_jsxs(YStack, { flex: 1, minH: 0, position: "relative", children: [_jsxs(XStack, { height: 52, shrink: 0, items: "center", gap: "$3", px: "$3", borderBottomWidth: 1, borderColor: "$borderColor", children: [_jsx(Text, { render: "h1", fontSize: "$4", fontWeight: "600", color: "$ink", numberOfLines: 1, children: "Board" }), _jsxs(Select, { value: key || 'all', onValueChange: setKey, children: [_jsx(SelectTrigger, { "aria-label": "Board", width: 140, $sm: { width: 100 }, children: _jsx(SelectValue, {}) }), _jsxs(SelectContent, { children: [_jsx(SelectItem, { value: "all", children: "All Boards" }, "all"), keys.map((k) => (_jsx(SelectItem, { value: k, children: k.charAt(0).toUpperCase() + k.slice(1) }, k)))] })] }), _jsx(YStack, { flex: 1 }), cards ? (_jsx(Text, { fontSize: "$1", color: "$soft", display: "none", $md: { display: 'flex' }, children: narrowed ? `${shown.length} of ${cards.length}` : `${cards.length} cards` })) : null, _jsx(Act, { icon: ListFilter, label: "Filter", on: filtering || narrowed, onPress: () => setFiltering((v) => !v) }), _jsx(XStack, { display: "none", $md: { display: 'flex' }, children: _jsx(Act, { icon: SquareTerminal, label: "Tabs & Shell", on: Boolean(aside), onPress: () => showAside(aside ? null : "tasks") }) })] }), _jsxs(XStack, { flex: 1, minH: 0, children: [view === 'planner' ? _jsx(Planner, { cards: cards ?? [], onOpen: setOpened }) : null, _jsxs(YStack, { flex: 1, minW: 0, minH: 0,
434
434
  // One view at a time: the planner is the other reading of these cards.
435
- display: view === 'planner' ? "none" : "flex", children: [refused ? (_jsxs(XStack, { items: "center", gap: "$2", mx: "$4", mt: "$3", px: "$3", py: "$2", rounded: "$3", borderWidth: 1, borderColor: "$borderColor", bg: "$hover", children: [_jsxs(Text, { fontSize: "$1", color: "$soft", flex: 1, minW: 0, children: [refused, " Cards will appear here when it answers."] }), _jsx(Box, { render: "button", onClick: () => bump((x) => x + 1), shrink: 0, px: "$2", py: "$1", rounded: "$2", borderWidth: 1, borderColor: "$borderColor", hoverStyle: { bg: '$hover' }, children: _jsx(Text, { fontSize: "$1", color: "$ink", children: "Try again" }) })] })) : null, _jsx(XStack, { flex: 1, minH: 0, overflow: "scroll", p: "$4", gap: "$3", items: "flex-start", children: COLUMNS.map((c) => (_jsx(Column, { column: c, cards: filed.get(c.id) ?? [], people: roster, onDrop: move, onOpen: setOpened, onAdd: add, loading: cards === null && !refused }, c.id))) })] })] }), _jsxs(XStack, { shrink: 0, items: "center", gap: "$2", px: "$3", py: "$2", borderTopWidth: 1, borderColor: "$borderColor", children: [_jsx(Act, { icon: Columns3, label: "Board", on: view === 'board', onPress: () => setView('board') }), _jsx(Act, { icon: Calendar, label: "Planner", on: view === 'planner', onPress: () => setView('planner') }), _jsx(YStack, { flex: 1 }), _jsx(Text, { fontSize: "$1", color: "$soft", numberOfLines: 1, display: "none", $md: { display: 'flex' }, children: "A card is a forge issue \u2014 moving it here moves it there." })] }), filtering ? (_jsx(Filter, { want: want, labels: labels, onWant: setWant, onClose: () => setFiltering(false) })) : null, opened ? (_jsx(Detail, { card: cards?.find((c) => c.id === opened.id) ?? opened, people: roster, agents: agents, onClose: () => setOpened(null), onSaved: () => bump((x) => x + 1) })) : null] }));
435
+ display: view === 'planner' ? "none" : "flex", children: [refused ? (_jsxs(XStack, { items: "center", gap: "$2", mx: "$4", mt: "$3", px: "$3", py: "$2", rounded: "$3", borderWidth: 1, borderColor: "$borderColor", bg: "$hover", children: [_jsxs(Text, { fontSize: "$1", color: "$soft", flex: 1, minW: 0, children: [refused, " Cards will appear here when it answers."] }), _jsx(Box, { render: "button", onClick: () => bump((x) => x + 1), shrink: 0, px: "$2", py: "$1", rounded: "$2", borderWidth: 1, borderColor: "$borderColor", hoverStyle: { bg: '$hover' }, children: _jsx(Text, { fontSize: "$1", color: "$ink", children: "Try again" }) })] })) : null, _jsx(XStack, { flex: 1, minH: 0, overflowX: "auto", overflowY: "auto", p: "$4", gap: "$3", items: "flex-start", children: COLUMNS.map((c) => (_jsx(Column, { column: c, cards: filed.get(c.id) ?? [], people: roster, onDrop: move, onOpen: setOpened, onAdd: add, loading: cards === null && !refused }, c.id))) })] })] }), _jsxs(XStack, { shrink: 0, items: "center", gap: "$2", px: "$3", py: "$2", borderTopWidth: 1, borderColor: "$borderColor", children: [_jsx(Act, { icon: Columns3, label: "Board", on: view === 'board', onPress: () => setView('board') }), _jsx(Act, { icon: Calendar, label: "Planner", on: view === 'planner', onPress: () => setView('planner') }), _jsx(YStack, { flex: 1 }), _jsx(Text, { fontSize: "$1", color: "$soft", numberOfLines: 1, display: "none", $md: { display: 'flex' }, children: "A card is a forge issue \u2014 moving it here moves it there." })] }), filtering ? (_jsx(Filter, { want: want, labels: labels, onWant: setWant, onClose: () => setFiltering(false) })) : null, opened ? (_jsx(Detail, { card: cards?.find((c) => c.id === opened.id) ?? opened, people: roster, agents: agents, onClose: () => setOpened(null), onSaved: () => bump((x) => x + 1) })) : null] }));
436
436
  }
437
437
  /**
438
438
  * The pane, and the session its reads need.
package/dist/Boxes.js CHANGED
@@ -169,5 +169,5 @@ export function Boxes() {
169
169
  return _jsx(Empty, { children: say(failed, 'this list') });
170
170
  if (!boxes)
171
171
  return _jsx(Empty, { children: "Reading your leases." });
172
- return (_jsxs(YStack, { flex: 1, minH: 0, children: [_jsx(ChannelHeader, { name: "Sandboxes", children: _jsxs(Text, { fontSize: "$2", color: "$soft", children: ["from ", usd(HOURLY.exec), "/hr"] }) }), _jsx(Start, { onDone: reload }), boxes.length === 0 ? (_jsx(Empty, { children: "Nothing is running. A sandbox is leased when an agent needs somewhere to work, and billed for the hours it is held." })) : (_jsx(YStack, { flex: 1, minH: 0, overflow: "scroll", p: "$4", gap: "$3", children: boxes.map((box) => (_jsx(Row, { box: box, busy: busy[box.id], onResume: () => void act(box.id, 'resume'), onStop: () => void act(box.id, 'stop'), onEnd: () => void act(box.id, 'end') }, box.id))) }))] }));
172
+ return (_jsxs(YStack, { flex: 1, minH: 0, children: [_jsx(ChannelHeader, { name: "Sandboxes", children: _jsxs(Text, { fontSize: "$2", color: "$soft", children: ["from ", usd(HOURLY.exec), "/hr"] }) }), _jsx(Start, { onDone: reload }), boxes.length === 0 ? (_jsx(Empty, { children: "Nothing is running. A sandbox is leased when an agent needs somewhere to work, and billed for the hours it is held." })) : (_jsx(YStack, { flex: 1, minH: 0, overflowY: "auto", overflowX: "hidden", p: "$4", gap: "$3", children: boxes.map((box) => (_jsx(Row, { box: box, busy: busy[box.id], onResume: () => void act(box.id, 'resume'), onStop: () => void act(box.id, 'stop'), onEnd: () => void act(box.id, 'end') }, box.id))) }))] }));
173
173
  }
package/dist/Chat.js CHANGED
@@ -32,7 +32,8 @@ import { capped, detail, familyOf, planRequired, plainly, refused } from "./fail
32
32
  import { Prose } from "./Prose.js";
33
33
  import { useAgent, useAi, useChat, useModels, } from "@hanzo/ai/react";
34
34
  import { freeCopy, grantConsent, hasConsent, isFree, } from "@hanzo/ai";
35
- import { ASK, Chat as ChatSurface, Code, Failure } from "@hanzo/ui/chat";
35
+ import { ASK, Chat as ChatSurface, Code, Failure, Parts, words } from "@hanzo/ui/chat";
36
+ import { compose, hold, images, partsOf } from "./lib/attach.js";
36
37
  import { Face, brief as roomBrief, join, roleOf, roomTurn, roster as rosterLine, rosterOf, speakers, speaksOf, voiceOf } from "./cast.js";
37
38
  import { SpeakButton, speakAgent } from "./speech.js";
38
39
  import { Crew, HOUSE, useCrew } from "./crew.js";
@@ -47,7 +48,7 @@ import { useHydrated } from "./lib/hydrated.js";
47
48
  import { base, served } from "./lib/ai.js";
48
49
  import { onServed } from "./lib/served.js";
49
50
  import { speech, useVoice, Voice } from "@hanzo/voice";
50
- import { ArrowUp, Mic, Square, Star, PanelRight, X } from "lucide-react";
51
+ import { ArrowUp, Mic, Paperclip, Square, Star, PanelRight, X } from "lucide-react";
51
52
  import { ENSO, FREE } from "./lib/ai.js";
52
53
  import { openThread, useOpen } from "./open.js";
53
54
  import { Beside, Framed } from "./Shell.js";
@@ -62,7 +63,7 @@ import { checkoutUrl, onFree, planName } from './lib/plans.js';
62
63
  import { Upgrade, useFree } from './Upgrade.js';
63
64
  import { enter, LOGIN } from './lib/destination.js';
64
65
  import { first } from './lib/first.js';
65
- import { mix } from "./lib/mix.js";
66
+ import { BAD, mix } from "./lib/mix.js";
66
67
  import { site } from './where.js';
67
68
  /** The veil under the free lane's consent sheet. */
68
69
  const SCRIM = mix("var(--pure-black)", 72, "srgb");
@@ -588,12 +589,31 @@ function Thread({ placeholder = MESSAGE, initial, anonymous = false, heading, })
588
589
  // identity changes on every `setMessages` — `useChat` closes over `messages` —
589
590
  // so an effect depending on it re-runs after each streamed token.
590
591
  const latest = useRef(send);
591
- latest.current = send;
592
+ // The images held for a turn ride with it at the moment it is sent, queued or
593
+ // not, and are spent once: a retry is the words again, not the pictures.
594
+ latest.current = (text) => send(text, outgoing.current.splice(0));
592
595
  // THE COMPOSER'S DRAFT IS HELD HERE, so an arriving question can be typed into
593
596
  // it. `Chat` owns a draft of its own and spreads `composer` after it, so
594
597
  // passing value/onChange/onSend takes that ownership over rather than fighting
595
598
  // it.
596
599
  const [draft, setDraft] = useState("");
600
+ // FILES WITH THE NEXT MESSAGE: pressed in through the paperclip, or dropped
601
+ // anywhere on the room. An image rides as a picture the model reads, a text
602
+ // file as its words (lib/attach); anything else is refused by name here.
603
+ const [files, setFiles] = useState([]);
604
+ const [fileSaid, setFileSaid] = useState(null);
605
+ const [dragging, setDragging] = useState(false);
606
+ const picker = useRef(null);
607
+ const outgoing = useRef([]);
608
+ const depth = useRef(0);
609
+ const take = useCallback(async (list) => {
610
+ if (!list?.length)
611
+ return;
612
+ const out = await Promise.all(Array.from(list).map(hold));
613
+ setFiles((now) => [...now, ...out.filter((one) => typeof one !== "string")]);
614
+ const refused = out.filter((one) => typeof one === "string");
615
+ setFileSaid(refused.length ? refused.join(" ") : null);
616
+ }, []);
597
617
  useSeed(initial, setDraft);
598
618
  // DICTATION, the same one the site's own composer offers. `speech()` defaults
599
619
  // to the Hanzo gateway and `base()` is the one place a host is read from, so
@@ -771,6 +791,17 @@ function Thread({ placeholder = MESSAGE, initial, anonymous = false, heading, })
771
791
  }
772
792
  speak(text);
773
793
  }, [model, speak]);
794
+ /** The draft and the held files, sent as one turn. */
795
+ const submit = () => {
796
+ const text = draft.trim();
797
+ if (!text && !files.length)
798
+ return;
799
+ outgoing.current = images(files);
800
+ setDraft("");
801
+ setFiles([]);
802
+ setFileSaid(null);
803
+ guarded(compose(text, files));
804
+ };
774
805
  /*
775
806
  WHAT AN UNANSWERED QUESTION LOOKS LIKE: a turn of its own directly under it,
776
807
  naming who was asked, saying what went wrong in the reader's words, and
@@ -803,7 +834,7 @@ function Thread({ placeholder = MESSAGE, initial, anonymous = false, heading, })
803
834
  }, [said, chat.streaming, queued]);
804
835
  const failure = (question) => {
805
836
  const failed = failures[question.id];
806
- const text = String(question.content ?? "");
837
+ const text = words(question.content);
807
838
  const whom = addressed(text, room);
808
839
  const wrong = failed?.error ?? null;
809
840
  const how = wrong ? refusalOf(wrong) : null;
@@ -856,7 +887,31 @@ function Thread({ placeholder = MESSAGE, initial, anonymous = false, heading, })
856
887
  }, onCancel: () => {
857
888
  setAsking(null);
858
889
  } }));
859
- return (_jsxs(YStack, { flex: 1, minH: 0, width: "100%", position: "relative", children: [_jsxs(XStack, { height: 48, items: "center", justify: "space-between", px: "$3", borderBottomWidth: 1, borderColor: "$borderColor", children: [_jsxs(XStack, { items: "center", gap: "$2", minW: 0, children: [_jsx(Box, { render: "button", onClick: () => open && star(open), "aria-label": open && starred.has(open) ? "Unstar this conversation" : "Star this conversation", "aria-pressed": !!open && starred.has(open), p: "$1.5", rounded: "$2", hoverStyle: { bg: '$hover' }, children: _jsx(Star, { size: 15, "aria-hidden": true, color: open && starred.has(open) ? "var(--foreground)" : "var(--muted-foreground)", fill: open && starred.has(open) ? "currentColor" : "none" }) }), 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, { items: "center", gap: "$1", children: [_jsx(Box, { render: "button", onClick: () => {
890
+ return (_jsxs(YStack, { flex: 1, minH: 0, width: "100%", position: "relative", "data-slot": "chat-room",
891
+ // Entered and left once for every child crossed, so the veil follows a
892
+ // count rather than the last event.
893
+ onDragEnter: (e) => {
894
+ if (!Array.from(e.dataTransfer.types).includes("Files"))
895
+ return;
896
+ depth.current += 1;
897
+ setDragging(true);
898
+ }, onDragOver: (e) => {
899
+ if (!Array.from(e.dataTransfer.types).includes("Files"))
900
+ return;
901
+ e.preventDefault();
902
+ e.dataTransfer.dropEffect = "copy";
903
+ }, onDragLeave: () => {
904
+ depth.current = Math.max(0, depth.current - 1);
905
+ if (depth.current === 0)
906
+ setDragging(false);
907
+ }, onDrop: (e) => {
908
+ depth.current = 0;
909
+ setDragging(false);
910
+ if (!e.dataTransfer.files?.length)
911
+ return;
912
+ e.preventDefault();
913
+ void take(e.dataTransfer.files);
914
+ }, 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: _jsx(Text, { fontSize: "$4", color: "$ink", children: "Drop to attach" }) })) : null, _jsxs(XStack, { height: 48, items: "center", justify: "space-between", px: "$3", borderBottomWidth: 1, borderColor: "$borderColor", children: [_jsxs(XStack, { items: "center", gap: "$2", minW: 0, children: [_jsx(Box, { render: "button", onClick: () => open && star(open), "aria-label": open && starred.has(open) ? "Unstar this conversation" : "Star this conversation", "aria-pressed": !!open && starred.has(open), p: "$1.5", rounded: "$2", hoverStyle: { bg: '$hover' }, children: _jsx(Star, { size: 15, "aria-hidden": true, color: open && starred.has(open) ? "var(--foreground)" : "var(--muted-foreground)", fill: open && starred.has(open) ? "currentColor" : "none" }) }), 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, { items: "center", gap: "$1", children: [_jsx(Box, { render: "button", onClick: () => {
860
915
  if (aside === null) {
861
916
  showAside("tasks");
862
917
  if (!pinned)
@@ -889,12 +944,15 @@ function Thread({ placeholder = MESSAGE, initial, anonymous = false, heading, })
889
944
  const question = shown.find((one) => one.id === String(turn.id).slice(FAILED.length + 1));
890
945
  return question ? failure(question) : null;
891
946
  }
947
+ // A QUESTION THAT CARRIED PICTURES draws them beside its words.
948
+ if (turn.role === "user" && Array.isArray(turn.content))
949
+ return _jsx(Parts, { parts: partsOf(turn.content), prose: (text) => _jsx(Prose, { text: text }) });
892
950
  const said = typeof turn.content === "string" ? turn.content : "";
893
951
  // WHO IS ANSWERING, while the answer has no words yet: the agents the
894
952
  // question addresses, or the whole room when it names nobody.
895
953
  if (turn.role === "assistant" && !said && chat.streaming && room.length) {
896
954
  const q = [...shown].reverse().find((one) => one.role === "user");
897
- const whom = addressed(String(q?.content ?? ""), room);
955
+ const whom = addressed(words(q?.content), room);
898
956
  return (_jsxs(Text, { fontSize: "$2", color: "$soft", children: [join(whom), " ", whom.length > 1 ? "are" : "is", " answering\u2026"] }));
899
957
  }
900
958
  // A ROOM'S ANSWER IS SEVERAL PEOPLE'S. Each opens a line with their
@@ -907,7 +965,7 @@ function Thread({ placeholder = MESSAGE, initial, anonymous = false, heading, })
907
965
  const at = shown.findIndex((one) => one.id === turn.id);
908
966
  const q = shown.slice(0, at).reverse().find((one) => one.role === "user");
909
967
  const silent = room.length > 1 && !(chat.streaming && at === shown.length - 1)
910
- ? addressed(String(q?.content ?? ""), room).filter((name) => !parts.some((part) => part.who?.toLowerCase() === name.toLowerCase()))
968
+ ? addressed(words(q?.content), room).filter((name) => !parts.some((part) => part.who?.toLowerCase() === name.toLowerCase()))
911
969
  : [];
912
970
  return (_jsxs(YStack, { gap: "$3", children: [parts.map((part, i) => (_jsxs(YStack, { gap: "$1", children: [part.who ? (_jsxs(XStack, { items: "center", gap: "$2", children: [_jsx(Face, { src: faces[part.who]?.avatar, emoji: faces[part.who]?.emoji, name: part.who, size: 20 }), _jsx(Text, { fontSize: "$2", fontWeight: "600", color: "$ink", children: part.who })] })) : null, _jsx(Prose, { text: part.text }), part.text ? (_jsxs(XStack, { ml: "$-1", items: "center", gap: "$2", children: [_jsx(Take, { text: part.text, says: "this answer" }), _jsx(SpeakButton, { text: part.text, agentName: part.who ?? withWhom ?? undefined })] })) : null] }, i))), silent.length ? (_jsxs(Text, { fontSize: "$2", color: "$soft", children: ["No answer from ", join(silent), " in this reply."] })) : null, byline(turn.id)] }));
913
971
  }
@@ -926,13 +984,7 @@ function Thread({ placeholder = MESSAGE, initial, anonymous = false, heading, })
926
984
  column: { maxW: MEASURE }, composer: {
927
985
  value: draft,
928
986
  onChange: setDraft,
929
- onSend: () => {
930
- const text = draft.trim();
931
- if (!text)
932
- return;
933
- setDraft("");
934
- guarded(text);
935
- },
987
+ onSend: submit,
936
988
  // A CHARACTER IS WHO YOU ARE WRITING TO, so the field says so — and
937
989
  // `label` keeps the accessible name constant while it does, which is
938
990
  // the reason the component ships both: a placeholder that changes with
@@ -982,15 +1034,16 @@ function Thread({ placeholder = MESSAGE, initial, anonymous = false, heading, })
982
1034
  // package's button and `streaming` still has to become stop; it keeps
983
1035
  // `data-slot="composer-send"` so a driver finds it, and it is the same
984
1036
  // `Control` circle as the mic, filled.
985
- send: (_jsxs(XStack, { items: "center", gap: "$2", children: [_jsx(Enso, { effort: effort, onEffort: setEffort, model: wanted, onModel: choose, models: offered }), _jsx(Control, { asChild: true, size: ROUND, children: _jsx(Voice, { voice: voice, children: (state) => (_jsx(Mic, { size: 15, fill: state === "idle" ? "none" : "currentColor" })) }) }), _jsx(Control, { type: "button", size: ROUND, fill: true, "data-slot": "composer-send", "aria-label": chat.streaming ? "Stop" : "Send", opacity: chat.streaming || draft.trim() ? 1 : 0.4, onClick: () => {
986
- if (chat.streaming)
987
- return chat.stop();
988
- const text = draft.trim();
989
- if (!text)
990
- return;
991
- setDraft("");
992
- guarded(text);
993
- }, children: chat.streaming ? (_jsx(Square, { size: 13, fill: "currentColor" })) : (_jsx(ArrowUp, { size: 15, strokeWidth: 2.5 })) })] })),
1037
+ // THE PAPERCLIP opens the file picker; the same files can be dropped on
1038
+ // the room.
1039
+ children: (_jsxs(_Fragment, { children: [_jsx(Control, { type: "button", size: ROUND, "aria-label": "Attach files", onClick: () => picker.current?.click(), children: _jsx(Paperclip, { size: 15, "aria-hidden": true }) }), _jsx("input", { ref: picker, type: "file", multiple: true, hidden: true, "data-slot": "composer-files", onChange: (e) => {
1040
+ void take(e.target.files);
1041
+ e.target.value = "";
1042
+ } })] })),
1043
+ // WHAT GOES WITH THE MESSAGE, above the frame: one chip a file, each
1044
+ // with its own remove, and the reason any file was refused.
1045
+ head: files.length || fileSaid ? (_jsxs(YStack, { width: "calc(100% - 32px)", maxW: `calc(${MEASURE} - 3rem)`, mx: "auto", gap: "$1.5", children: [files.length ? (_jsx(XStack, { gap: "$1.5", flexWrap: "wrap", role: "list", "aria-label": "Attached files", "data-slot": "composer-attached", children: files.map((one) => (_jsxs(XStack, { role: "listitem", items: "center", gap: "$1.5", pl: "$2.5", pr: "$1", py: "$1", rounded: "$10", borderWidth: 1, borderColor: "$borderColor", bg: "$hover", maxW: 260, children: [_jsx(Paperclip, { size: 12, "aria-hidden": true, color: "var(--muted-foreground)" }), _jsx(Text, { fontSize: "$2", color: "$ink", numberOfLines: 1, children: one.name }), _jsx(Box, { render: "button", "aria-label": `Remove ${one.name}`, onClick: () => setFiles((now) => now.filter((f) => f.id !== one.id)), p: "$1", rounded: "$10", hoverStyle: { bg: "$raised" }, children: _jsx(X, { size: 12, "aria-hidden": true }) })] }, one.id))) })) : null, fileSaid ? (_jsx(Text, { role: "alert", fontSize: "$2", color: BAD, children: fileSaid })) : null] })) : undefined,
1046
+ send: (_jsxs(XStack, { items: "center", gap: "$2", children: [_jsx(Enso, { effort: effort, onEffort: setEffort, model: wanted, onModel: choose, models: offered }), _jsx(Control, { asChild: true, size: ROUND, children: _jsx(Voice, { voice: voice, children: (state) => (_jsx(Mic, { size: 15, fill: state === "idle" ? "none" : "currentColor" })) }) }), _jsx(Control, { type: "button", size: ROUND, fill: true, "data-slot": "composer-send", "aria-label": chat.streaming ? "Stop" : "Send", opacity: chat.streaming || draft.trim() || files.length ? 1 : 0.4, onClick: () => (chat.streaming ? chat.stop() : submit()), children: chat.streaming ? (_jsx(Square, { size: 13, fill: "currentColor" })) : (_jsx(ArrowUp, { size: 15, strokeWidth: 2.5 })) })] })),
994
1047
  // WHAT THE PLAN HAS LEFT, under the frame and at its measure, for a
995
1048
  // plan holder only: Free keeps the free lane's own notice.
996
1049
  foot: held && limits && (limits.session || limits.day || limits.month) ? (_jsx(YStack, { width: "calc(100% - 32px)", maxW: `calc(${MEASURE} - 3rem)`, mx: "auto", children: _jsx(Meters, { limits: limits }) })) : undefined,
package/dist/Contacts.js CHANGED
@@ -226,7 +226,7 @@ function Roster({ owner }) {
226
226
  }
227
227
  }, [ai, busy, entries, picked, router]);
228
228
  const waiting = (reading && people.length === 0) || (gathering && agents.length === 0);
229
- return (_jsxs(YStack, { flex: 1, minH: 0, children: [_jsxs(XStack, { height: 48, shrink: 0, items: "center", gap: "$2", px: "$3", borderBottomWidth: 1, borderColor: "$borderColor", children: [_jsx(Text, { render: "h1", flex: 1, fontSize: "$3", fontWeight: "600", color: "$ink", numberOfLines: 1, children: "Contacts" }), _jsx(Control, { onPress: () => setInviting(true), icon: _jsx(UserPlus, { size: 13, "aria-hidden": true }), children: "Invite" }), _jsx(Control, { onPress: () => setHiring(true), icon: _jsx(Plus, { size: 13, "aria-hidden": true }), children: "New agent" })] }), _jsxs(XStack, { shrink: 0, items: "center", gap: "$2", px: "$3", py: "$2", borderBottomWidth: 1, borderColor: "$borderColor", flexWrap: "wrap", children: [_jsxs(XStack, { items: "center", gap: "$1.5", flex: 1, minW: 160, px: "$2", borderWidth: 1, borderColor: "$borderColor", rounded: "$3", children: [_jsx(Search, { size: 13, "aria-hidden": true }), _jsx(Text, { render: _jsx("input", { value: look, onChange: (e) => setLook(e.target.value), placeholder: "Search", "aria-label": "Search contacts" }), flex: 1, minW: 0, bg: "transparent", borderWidth: 0, outlineStyle: "none", color: "inherit", fontSize: "$2", px: 0, py: 6 })] }), TABS.map((one) => (_jsx(Pill, { on: tab === one.id, onPress: () => setTab(one.id), children: one.label }, one.id))), _jsx(Box, { width: 1, height: 18, bg: "$borderColor" }), _jsx(Pill, { on: order === 'name', onPress: () => setOrder('name'), children: "A\u2013Z" }), _jsx(Pill, { on: order === 'recent', onPress: () => setOrder('recent'), children: "Recent" })] }), unread ? _jsx(Note, { children: say(unread, 'your organization') }) : null, ungathered ? _jsx(Note, { children: say(ungathered, "this org's agents") }) : null, unlisted ? _jsx(Note, { children: say(unlisted, 'the agent presets') }) : null, wrong ? _jsx(Note, { children: wrong }) : null, _jsx(YStack, { flex: 1, minH: 0, overflow: "scroll", children: waiting && shown.length === 0 ? (_jsx(Note, { children: "Reading your directory." })) : shown.length === 0 ? (_jsx(Note, { children: look.trim()
229
+ return (_jsxs(YStack, { flex: 1, minH: 0, children: [_jsxs(XStack, { height: 48, shrink: 0, items: "center", gap: "$2", px: "$3", borderBottomWidth: 1, borderColor: "$borderColor", children: [_jsx(Text, { render: "h1", flex: 1, fontSize: "$3", fontWeight: "600", color: "$ink", numberOfLines: 1, children: "Contacts" }), _jsx(Control, { onPress: () => setInviting(true), icon: _jsx(UserPlus, { size: 13, "aria-hidden": true }), children: "Invite" }), _jsx(Control, { onPress: () => setHiring(true), icon: _jsx(Plus, { size: 13, "aria-hidden": true }), children: "New agent" })] }), _jsxs(XStack, { shrink: 0, items: "center", gap: "$2", px: "$3", py: "$2", borderBottomWidth: 1, borderColor: "$borderColor", flexWrap: "wrap", children: [_jsxs(XStack, { items: "center", gap: "$1.5", flex: 1, minW: 160, px: "$2", borderWidth: 1, borderColor: "$borderColor", rounded: "$3", children: [_jsx(Search, { size: 13, "aria-hidden": true }), _jsx(Text, { render: _jsx("input", { value: look, onChange: (e) => setLook(e.target.value), placeholder: "Search", "aria-label": "Search contacts" }), flex: 1, minW: 0, bg: "transparent", borderWidth: 0, outlineStyle: "none", color: "inherit", fontSize: "$2", px: 0, py: 6 })] }), TABS.map((one) => (_jsx(Pill, { on: tab === one.id, onPress: () => setTab(one.id), children: one.label }, one.id))), _jsx(Box, { width: 1, height: 18, bg: "$borderColor" }), _jsx(Pill, { on: order === 'name', onPress: () => setOrder('name'), children: "A\u2013Z" }), _jsx(Pill, { on: order === 'recent', onPress: () => setOrder('recent'), children: "Recent" })] }), unread ? _jsx(Note, { children: say(unread, 'your organization') }) : null, ungathered ? _jsx(Note, { children: say(ungathered, "this org's agents") }) : null, unlisted ? _jsx(Note, { children: say(unlisted, 'the agent presets') }) : null, wrong ? _jsx(Note, { children: wrong }) : null, _jsx(YStack, { flex: 1, minH: 0, overflowY: "auto", overflowX: "hidden", children: waiting && shown.length === 0 ? (_jsx(Note, { children: "Reading your directory." })) : shown.length === 0 ? (_jsx(Note, { children: look.trim()
230
230
  ? `Nobody here matches “${look.trim()}”.`
231
231
  : tab === 'agent'
232
232
  ? 'No agents yet. New agent writes one.'
package/dist/Directory.js CHANGED
@@ -297,7 +297,7 @@ export function Catalog({ tab }) {
297
297
  return r.name.toLowerCase().includes(q) || r.description.toLowerCase().includes(q);
298
298
  });
299
299
  }, [rows, query]);
300
- return (_jsxs(YStack, { gap: "$4", children: [_jsx(XStack, { items: "center", gap: "$2", flexWrap: "wrap", children: _jsxs(XStack, { items: "center", gap: "$2", flex: 1, minW: 180, children: [_jsx(Search, { size: 14, "aria-hidden": true }), _jsx(Input, { value: query, onChangeText: setQuery, placeholder: `Search ${tab}`, "aria-label": `Search ${tab}`, flex: 1 })] }) }), shown.length > 0 ? (_jsxs(_Fragment, { children: [_jsxs(Text, { fontSize: "$3", color: "$soft", children: [shown.length, " ", shown.length === 1 ? 'result' : 'results'] }), _jsx(YStack, { maxH: 380, overflow: "scroll", pr: "$1", width: "100%", children: _jsx(Grid, { columns: tab === 'channels' ? { min: 168, max: 4 } : { min: 240, max: 2 }, gap: 12, children: shown.map((r) => tab === 'channels' ? (_jsx(Channel, { row: r, onChange: () => setTurn((n) => n + 1) }, r.id)) : (_jsx(RowCard, { row: r, tab: tab }, r.id))) }) })] })) : (_jsxs(YStack, { gap: "$3", py: "$4", children: [_jsx(Text, { fontSize: "$3", color: "$soft", children: query ? `Nothing matches “${query}”.` : `No ${label.toLowerCase()} here yet.` }), !query ? (_jsx(Action, { href: `${CONSOLE}/${tab}`, target: "_blank", rel: "noreferrer", $touchable: { minH: 44, minW: 44 }, children: "Add in console" })) : null] }))] }));
300
+ return (_jsxs(YStack, { gap: "$4", children: [_jsx(XStack, { items: "center", gap: "$2", flexWrap: "wrap", children: _jsxs(XStack, { items: "center", gap: "$2", flex: 1, minW: 180, children: [_jsx(Search, { size: 14, "aria-hidden": true }), _jsx(Input, { value: query, onChangeText: setQuery, placeholder: `Search ${tab}`, "aria-label": `Search ${tab}`, flex: 1 })] }) }), shown.length > 0 ? (_jsxs(_Fragment, { children: [_jsxs(Text, { fontSize: "$3", color: "$soft", children: [shown.length, " ", shown.length === 1 ? 'result' : 'results'] }), _jsx(YStack, { maxH: 380, overflowY: "auto", overflowX: "hidden", pr: "$1", width: "100%", children: _jsx(Grid, { columns: tab === 'channels' ? { min: 168, max: 4 } : { min: 240, max: 2 }, gap: 12, children: shown.map((r) => tab === 'channels' ? (_jsx(Channel, { row: r, onChange: () => setTurn((n) => n + 1) }, r.id)) : (_jsx(RowCard, { row: r, tab: tab }, r.id))) }) })] })) : (_jsxs(YStack, { gap: "$3", py: "$4", children: [_jsx(Text, { fontSize: "$3", color: "$soft", children: query ? `Nothing matches “${query}”.` : `No ${label.toLowerCase()} here yet.` }), !query ? (_jsx(Action, { href: `${CONSOLE}/${tab}`, target: "_blank", rel: "noreferrer", $touchable: { minH: 44, minW: 44 }, children: "Add in console" })) : null] }))] }));
301
301
  }
302
302
  function RowCard({ row, tab }) {
303
303
  const { isAuthenticated } = useIam();
package/dist/Drive.js CHANGED
@@ -423,7 +423,7 @@ export function Drive() {
423
423
  setPrefix('');
424
424
  setMenu(false);
425
425
  }, children: [_jsx(Icon, { size: 16 }), item.label] }, item.id));
426
- }) }), _jsxs(YStack, { gap: "$2", mt: "$2", px: "$2", children: [_jsx(Text, { fontSize: 11, fontWeight: "600", color: "$faint", letterSpacing: 0.6, children: "Storage" }), _jsx(Text, { fontSize: 12, color: "$quiet", children: usage ? `${usage.capped ? 'over ' : ''}${weigh(usage.bytes)} in ${usage.files.toLocaleString()} file${usage.files === 1 ? '' : 's'}` : 'Measuring…' }), _jsx(Button, { asChild: true, size: "sm", mt: 8, children: _jsx("a", { href: checkoutUrl(), target: "_blank", rel: "noreferrer", children: "Manage storage" }) })] })] }), _jsxs(YStack, { px: 14, py: 12, rounded: 10, borderWidth: 1, borderColor: "$borderColor", bg: "$panel", gap: 6, children: [_jsxs(XStack, { items: "center", gap: "$2", children: [_jsx(Cloud, { size: 16, color: "var(--foreground)" }), _jsx(Text, { fontSize: "$2", fontWeight: "600", color: "$ink", children: "Hanzo Drive" })] }), _jsx(Text, { fontSize: "$1", color: "$soft", children: [userEmail, activeBucket].filter(Boolean).join(' · ') }), _jsx(Button, { asChild: true, variant: "link", size: "sm", px: 0, mt: 4, justify: "flex-start", children: _jsxs("a", { href: "https://docs.hanzo.ai/docs/openapi/s3/", target: "_blank", rel: "noreferrer", children: ["Documentation", _jsx(ArrowRight, { size: 12 })] }) })] })] }), _jsxs(YStack, { flex: 1, minH: 0, px: "$6", py: "$5", overflow: "scroll", gap: "$5", children: [bucket || prefix ? (_jsxs(XStack, { items: "center", gap: "$2", pb: "$2", borderBottomWidth: 1, borderColor: "$borderColor", children: [_jsx(Button, { ...CRUMB, color: "$soft", onClick: () => {
426
+ }) }), _jsxs(YStack, { gap: "$2", mt: "$2", px: "$2", children: [_jsx(Text, { fontSize: 11, fontWeight: "600", color: "$faint", letterSpacing: 0.6, children: "Storage" }), _jsx(Text, { fontSize: 12, color: "$quiet", children: usage ? `${usage.capped ? 'over ' : ''}${weigh(usage.bytes)} in ${usage.files.toLocaleString()} file${usage.files === 1 ? '' : 's'}` : 'Measuring…' }), _jsx(Button, { asChild: true, size: "sm", mt: 8, children: _jsx("a", { href: checkoutUrl(), target: "_blank", rel: "noreferrer", children: "Manage storage" }) })] })] }), _jsxs(YStack, { px: 14, py: 12, rounded: 10, borderWidth: 1, borderColor: "$borderColor", bg: "$panel", gap: 6, children: [_jsxs(XStack, { items: "center", gap: "$2", children: [_jsx(Cloud, { size: 16, color: "var(--foreground)" }), _jsx(Text, { fontSize: "$2", fontWeight: "600", color: "$ink", children: "Hanzo Drive" })] }), _jsx(Text, { fontSize: "$1", color: "$soft", children: [userEmail, activeBucket].filter(Boolean).join(' · ') }), _jsx(Button, { asChild: true, variant: "link", size: "sm", px: 0, mt: 4, justify: "flex-start", children: _jsxs("a", { href: "https://docs.hanzo.ai/docs/openapi/s3/", target: "_blank", rel: "noreferrer", children: ["Documentation", _jsx(ArrowRight, { size: 12 })] }) })] })] }), _jsxs(YStack, { flex: 1, minH: 0, px: "$6", py: "$5", overflowY: "auto", overflowX: "hidden", gap: "$5", children: [bucket || prefix ? (_jsxs(XStack, { items: "center", gap: "$2", pb: "$2", borderBottomWidth: 1, borderColor: "$borderColor", children: [_jsx(Button, { ...CRUMB, color: "$soft", onClick: () => {
427
427
  openBucket(null);
428
428
  setPrefix('');
429
429
  }, children: "Drive" }), _jsx(ChevronRight, { size: 13, color: "var(--muted-foreground)" }), _jsx(Button, { ...CRUMB, color: crumbs.length === 0 ? '$ink' : '$soft', onClick: () => setPrefix(''), children: bucket || 'hanzo' }), crumbs.map((c, i) => (_jsxs(React.Fragment, { children: [_jsx(ChevronRight, { size: 13, color: "var(--muted-foreground)" }), _jsx(Button, { ...CRUMB, color: i === crumbs.length - 1 ? '$ink' : '$soft', onClick: () => setPrefix(crumbs.slice(0, i + 1).map((x) => `${x}/`).join('')), children: c })] }, `${i}-${c}`)))] })) : null, _jsxs(XStack, { items: "center", justify: "space-between", children: [_jsxs(YStack, { gap: "$1", children: [_jsx(Text, { render: "h1", fontSize: 26, fontWeight: "700", color: "$ink", children: "Drive" }), _jsx(Text, { fontSize: 13, color: "$soft", children: "Secure. Private. Built for builders." })] }), _jsx(XStack, { items: "center", bg: "$panel", rounded: 8, borderWidth: 1, borderColor: "$borderColor", p: 2, gap: 2, children: [
package/dist/Home.js CHANGED
@@ -90,7 +90,7 @@ export function Home({ ask }) {
90
90
  const q = hints.length ? `${question} ${hints.map((s) => `@${s}`).join(' ')}` : question;
91
91
  window.location.href = `/chat?q=${encodeURIComponent(q)}`;
92
92
  };
93
- return (_jsxs(YStack, { "data-home": true, flex: 1, minH: 0, width: "100%", overflow: "scroll", children: [_jsxs(YStack, { width: "100%", maxW: 720, mx: "auto", px: "$4", pt: "$8", gap: "$4", $sm: { pt: '$10' }, children: [_jsx(Text, { render: "h1", fontSize: "$8", fontWeight: "500", color: INK, text: "center", $sm: { fontSize: '$9' }, children: "What are we building?" }), _jsx(XStack, { justify: "center", gap: "$1", flexWrap: "wrap", children: MODES.map((one) => (_jsx(Box, { "data-tab": one.id, render: "button", onClick: () => setMode(one.id), "aria-current": mode === one.id, px: "$3", py: "$2", rounded: "$10", bg: mode === one.id ? CHOSEN : 'transparent', hoverStyle: { bg: '$hover' }, children: _jsx(Text, { fontSize: "$3", fontWeight: "500", color: mode === one.id ? INK : SOFT, children: one.label }) }, one.id))) }), _jsx(Composer, { value: draft, onChange: setDraft, onSend: send, placeholder: here.asks, label: here.asks, rows: 2, maxHeight: 240, children: grounded ? (_jsx(XStack, { gap: "$1", flexWrap: "wrap", shrink: 1, minW: 0, children: SOURCES.map((s) => {
93
+ return (_jsxs(YStack, { "data-home": true, flex: 1, minH: 0, width: "100%", overflowY: "auto", overflowX: "hidden", children: [_jsxs(YStack, { width: "100%", maxW: 720, mx: "auto", px: "$4", pt: "$8", gap: "$4", $sm: { pt: '$10' }, children: [_jsx(Text, { render: "h1", fontSize: "$8", fontWeight: "500", color: INK, text: "center", $sm: { fontSize: '$9' }, children: "What are we building?" }), _jsx(XStack, { justify: "center", gap: "$1", flexWrap: "wrap", children: MODES.map((one) => (_jsx(Box, { "data-tab": one.id, render: "button", onClick: () => setMode(one.id), "aria-current": mode === one.id, px: "$3", py: "$2", rounded: "$10", bg: mode === one.id ? CHOSEN : 'transparent', hoverStyle: { bg: '$hover' }, children: _jsx(Text, { fontSize: "$3", fontWeight: "500", color: mode === one.id ? INK : SOFT, children: one.label }) }, one.id))) }), _jsx(Composer, { value: draft, onChange: setDraft, onSend: send, placeholder: here.asks, label: here.asks, rows: 2, maxHeight: 240, children: grounded ? (_jsx(XStack, { gap: "$1", flexWrap: "wrap", shrink: 1, minW: 0, children: SOURCES.map((s) => {
94
94
  const on = picked.includes(s);
95
95
  return (_jsx(Box, { "data-chip": s, render: "button", "aria-pressed": on, onClick: () => setPicked((held) => (on ? held.filter((h) => h !== s) : [...held, s])), px: "$2", py: "$1", rounded: "$10", bg: on ? CHOSEN : 'transparent', hoverStyle: { bg: '$hover' }, children: _jsxs(Text, { fontSize: "$2", color: on ? INK : SOFT, children: ["@", s] }) }, s));
96
96
  }) })) : null }), _jsx(Crew, { to: "/chat" })] }), _jsx(YStack, { width: "100%", maxW: 880, mx: "auto", px: "$4", py: "$8", children: _jsxs(Grid, { columns: { min: 260, max: 3 }, gap: 12, children: [_jsx(Rooms, { ...held }), _jsx(Projects, {}), _jsx(Chats, {}), _jsx(Next, { onConnect: () => setDirectory(true) }), _jsx(Apps, { onConnect: () => setDirectory(true) }), _jsx(Team, {}), _jsx(Build, {})] }) }), _jsx(Directory, { open: directory, onClose: () => setDirectory(false) })] }));
package/dist/Inbox.js CHANGED
@@ -86,7 +86,7 @@ function Empty({ children, offer }) {
86
86
  }
87
87
  /** The list, which stays on screen while you read down it. */
88
88
  function Arrivals({ rooms, open, onPick, }) {
89
- return (_jsxs(YStack, { flex: 1, minH: 0, children: [_jsxs(XStack, { height: 48, shrink: 0, items: "center", px: "$3", gap: "$2", borderBottomWidth: 1, borderColor: "$borderColor", children: [_jsx(Text, { flex: 1, fontSize: "$3", fontWeight: "600", color: "$ink", children: "Inbox" }), _jsx(Connect, {})] }), _jsx(YStack, { flex: 1, minH: 0, overflow: "scroll", children: rooms.map((one) => (_jsx(Box, { render: "button", onClick: () => onPick(one.key), "aria-current": one.key === open, display: "block", width: "100%", cursor: "pointer", borderBottomWidth: 1, borderColor: "var(--borderColor, var(--white-08))", bg: one.key === open ? 'var(--raised, var(--white-06))' : 'transparent', hoverStyle: { bg: '$hover' }, children: _jsxs(XStack, { gap: "$3", items: "center", p: "$3", children: [_jsx(Sender, { channel: one.channel }), _jsxs(YStack, { flex: 1, minW: 0, gap: "$1", items: "flex-start", children: [_jsxs(XStack, { width: "100%", items: "baseline", gap: "$2", children: [_jsx(Text, { fontSize: "$3", fontWeight: "600", color: "$ink", numberOfLines: 1, flex: 1, text: "left", children: one.title }), one.lastAt ? (_jsx(Text, { fontSize: "$1", color: "$soft", children: stamp(one.lastAt / 1000) })) : null] }), one.preview ? (_jsx(Text, { fontSize: "$2", color: "$soft", numberOfLines: 1, children: one.preview })) : null] })] }) }, one.key))) })] }));
89
+ return (_jsxs(YStack, { flex: 1, minH: 0, children: [_jsxs(XStack, { height: 48, shrink: 0, items: "center", px: "$3", gap: "$2", borderBottomWidth: 1, borderColor: "$borderColor", children: [_jsx(Text, { flex: 1, fontSize: "$3", fontWeight: "600", color: "$ink", children: "Inbox" }), _jsx(Connect, {})] }), _jsx(YStack, { flex: 1, minH: 0, overflowY: "auto", overflowX: "hidden", children: rooms.map((one) => (_jsx(Box, { render: "button", onClick: () => onPick(one.key), "aria-current": one.key === open, display: "block", width: "100%", cursor: "pointer", borderBottomWidth: 1, borderColor: "var(--borderColor, var(--white-08))", bg: one.key === open ? 'var(--raised, var(--white-06))' : 'transparent', hoverStyle: { bg: '$hover' }, children: _jsxs(XStack, { gap: "$3", items: "center", p: "$3", children: [_jsx(Sender, { channel: one.channel }), _jsxs(YStack, { flex: 1, minW: 0, gap: "$1", items: "flex-start", children: [_jsxs(XStack, { width: "100%", items: "baseline", gap: "$2", children: [_jsx(Text, { fontSize: "$3", fontWeight: "600", color: "$ink", numberOfLines: 1, flex: 1, text: "left", children: one.title }), one.lastAt ? (_jsx(Text, { fontSize: "$1", color: "$soft", children: stamp(one.lastAt / 1000) })) : null] }), one.preview ? (_jsx(Text, { fontSize: "$2", color: "$soft", numberOfLines: 1, children: one.preview })) : null] })] }) }, one.key))) })] }));
90
90
  }
91
91
  /** What is open, beside the list rather than instead of it. */
92
92
  function Reading({ room, reload, }) {
@@ -133,7 +133,7 @@ function Reading({ room, reload, }) {
133
133
  setSending(false);
134
134
  }
135
135
  }, [client, draft, sending, room.channel, room.roomId, reload]);
136
- return (_jsxs(YStack, { flex: 1, minH: 0, children: [_jsx(ChannelHeader, { name: roomName(room), children: _jsxs(XStack, { items: "center", gap: "$1.5", children: [_jsx(Network, { channel: room.channel, size: 14 }), _jsx(Text, { fontSize: "$2", color: "$soft", children: of(room.channel).name })] }) }), _jsx(YStack, { flex: 1, minH: 0, overflow: "scroll", p: "$4", gap: "$3", children: [...room.messages]
136
+ return (_jsxs(YStack, { flex: 1, minH: 0, children: [_jsx(ChannelHeader, { name: roomName(room), children: _jsxs(XStack, { items: "center", gap: "$1.5", children: [_jsx(Network, { channel: room.channel, size: 14 }), _jsx(Text, { fontSize: "$2", color: "$soft", children: of(room.channel).name })] }) }), _jsx(YStack, { flex: 1, minH: 0, overflowY: "auto", overflowX: "hidden", p: "$4", gap: "$3", children: [...room.messages]
137
137
  .sort((a, b) => a.createdAt - b.createdAt)
138
138
  .map((message) => (_jsxs(XStack, { gap: "$3", items: "flex-start", children: [_jsx(Sender, { channel: room.channel, size: 32 }), _jsxs(YStack, { flex: 1, minW: 0, gap: "$1", children: [_jsxs(XStack, { gap: "$2", items: "baseline", children: [_jsx(Text, { fontSize: "$3", fontWeight: "500", color: "$ink", children: speaker(room.channel, message.sender) }), _jsx(Text, { fontSize: "$1", color: "$soft", children: when(message.createdAt) })] }), _jsx(Text, { fontSize: "$3", color: "$ink", children: message.text })] })] }, message.id))) }), _jsx(YStack, { p: "$3", gap: "$2", borderTopWidth: 1, borderColor: "$borderColor", children: room.roomId ? (_jsxs(_Fragment, { children: [_jsx(Composer, { value: draft, onChange: setDraft, onSend: () => void send(), busy: sending, placeholder: `Reply on ${of(room.channel).name}`, label: `Reply on ${of(room.channel).name}` }), failed ? (_jsx(Text, { fontSize: "$2", color: BAD, children: failed })) : null] })) : (_jsx(Text, { fontSize: "$2", color: "$soft", children: "This message names no room to answer in." })) })] }));
139
139
  }
package/dist/Market.js CHANGED
@@ -141,7 +141,7 @@ export function Market({ scope }) {
141
141
  ? 'No agents installed yet.'
142
142
  : ask
143
143
  ? 'Nothing in the registry matches that.'
144
- : 'The shelf is empty — the catalog has not been synced yet.' })) : (_jsxs(YStack, { flex: 1, minH: 0, overflow: "scroll", p: "$5", gap: "$3", children: [_jsx(Text, { fontSize: "$1", color: "$soft", children: scope === 'installed'
144
+ : 'The shelf is empty — the catalog has not been synced yet.' })) : (_jsxs(YStack, { flex: 1, minH: 0, overflowY: "auto", overflowX: "hidden", p: "$5", gap: "$3", children: [_jsx(Text, { fontSize: "$1", color: "$soft", children: scope === 'installed'
145
145
  ? `${shown.length} installed`
146
146
  : ask
147
147
  ? `${shown.length} of ${total} matching`
package/dist/Me.js CHANGED
@@ -160,7 +160,7 @@ export function Me({ host, narrow = false, onOrg, }) {
160
160
  return null;
161
161
  return (_jsxs(_Fragment, { children: [_jsxs(Popover, { open: menu.open, onOpenChange: menu.onOpenChange, placement: narrow ? 'right-end' : 'top-start', allowFlip: true, stayInFrame: true, offset: 8, children: [_jsx(Popover.Anchor, { width: "100%", children: _jsxs(XStack, { ref: (el) => {
162
162
  row.current = el instanceof HTMLElement ? el : null;
163
- }, render: "button", "data-slot": "me", "aria-label": `Account: ${[who, current].filter(Boolean).join(' · ')}`, "aria-haspopup": "menu", "aria-expanded": menu.open, onPress: menu.toggle, width: narrow ? 40 : '100%', minH: narrow ? 40 : 48, px: narrow ? 0 : '$2', gap: "$2.5", rounded: narrow ? 999 : '$5', items: "center", justify: narrow ? 'center' : 'flex-start', hoverStyle: { bg: '$hover' }, focusVisibleStyle: { outlineWidth: 2, outlineStyle: 'solid', outlineColor: '$outlineColor' }, cursor: "pointer", children: [_jsx(Face, { host: host, size: narrow ? 30 : 32 }), narrow ? null : (_jsxs(_Fragment, { children: [_jsxs(YStack, { flex: 1, minW: 0, children: [_jsx(SizableText, { size: "$3", fontWeight: "600", color: "$ink", numberOfLines: 1, text: "left", children: who }), where ? (_jsx(SizableText, { size: "$1", color: "$soft", numberOfLines: 1, text: "left", children: where })) : null] }), _jsx(ChevronsUpDown, { size: 14, "aria-hidden": true, opacity: 0.6 })] }))] }) }), _jsx(Popover.Content, { ...glass(2), items: "stretch", borderWidth: 1, rounded: "$6", p: "$1.5", width: 288, maxW: "calc(100vw - 16px)", maxH: "calc(100dvh - 32px)", overflow: "scroll", children: _jsx(View, { render: _jsx("div", { ref: land, role: "menu", "aria-label": "Account", onKeyDown: (e) => rove(e, close) }), self: "stretch", minW: 0, children: _jsxs(YStack, { gap: 2, children: [_jsx(SizableText, { size: "$2", color: "$soft", px: "$2", py: "$1.5", numberOfLines: 1, children: host.person.email || who }), _jsx(Rule, {}), _jsx(Label, { children: "Organizations" }), _jsx(YStack, { role: "group", "aria-label": "Organizations", gap: 2, children: all.map((o) => (_jsx(Item, { icon: _jsx(Mark, { org: o }), label: o.display, sub: [o.personal ? 'Personal' : said(o.role), planFor(o)].filter(Boolean).join(' · '), checked: o.name === current, onPress: () => {
163
+ }, render: "button", "data-slot": "me", "aria-label": `Account: ${[who, current].filter(Boolean).join(' · ')}`, "aria-haspopup": "menu", "aria-expanded": menu.open, onPress: menu.toggle, width: narrow ? 40 : '100%', minH: narrow ? 40 : 48, px: narrow ? 0 : '$2', gap: "$2.5", rounded: narrow ? 999 : '$5', items: "center", justify: narrow ? 'center' : 'flex-start', hoverStyle: { bg: '$hover' }, focusVisibleStyle: { outlineWidth: 2, outlineStyle: 'solid', outlineColor: '$outlineColor' }, cursor: "pointer", children: [_jsx(Face, { host: host, size: narrow ? 30 : 32 }), narrow ? null : (_jsxs(_Fragment, { children: [_jsxs(YStack, { flex: 1, minW: 0, children: [_jsx(SizableText, { size: "$3", fontWeight: "600", color: "$ink", numberOfLines: 1, text: "left", children: who }), where ? (_jsx(SizableText, { size: "$1", color: "$soft", numberOfLines: 1, text: "left", children: where })) : null] }), _jsx(ChevronsUpDown, { size: 14, "aria-hidden": true, opacity: 0.6 })] }))] }) }), _jsx(Popover.Content, { ...glass(2), items: "stretch", borderWidth: 1, rounded: "$6", p: "$1.5", width: 288, maxW: "calc(100vw - 16px)", maxH: "calc(100dvh - 32px)", overflowY: "auto", overflowX: "hidden", children: _jsx(View, { render: _jsx("div", { ref: land, role: "menu", "aria-label": "Account", onKeyDown: (e) => rove(e, close) }), self: "stretch", minW: 0, children: _jsxs(YStack, { gap: 2, children: [_jsx(SizableText, { size: "$2", color: "$soft", px: "$2", py: "$1.5", numberOfLines: 1, children: host.person.email || who }), _jsx(Rule, {}), _jsx(Label, { children: "Organizations" }), _jsx(YStack, { role: "group", "aria-label": "Organizations", gap: 2, children: all.map((o) => (_jsx(Item, { icon: _jsx(Mark, { org: o }), label: o.display, sub: [o.personal ? 'Personal' : said(o.role), planFor(o)].filter(Boolean).join(' · '), checked: o.name === current, onPress: () => {
164
164
  menu.onOpenChange(false);
165
165
  if (o.name !== current)
166
166
  onOrg(o.name);
package/dist/Profile.js CHANGED
@@ -55,7 +55,7 @@ export function Profile({ open, onClose }) {
55
55
  return (_jsxs(YStack, { ...pane(sheet(2).backgroundColor), width: WIDTH, maxW: "100%", shrink: 0, minH: 0,
56
56
  // A column on a laptop. On a phone the room is already the whole screen,
57
57
  // so this covers it rather than squeezing it to nothing.
58
- position: "absolute", t: 0, r: 0, b: 0, l: 0, z: "var(--z-panel)", $lg: { position: 'relative', z: 'var(--z-base)', l: 'auto' }, children: [_jsxs(XStack, { items: "center", px: "$4", py: "$3", gap: "$2", children: [_jsx(Text, { flex: 1, fontSize: "$5", fontWeight: "600", color: "$ink", children: "Profile" }), _jsx(Box, { render: "button", onClick: onClose, "aria-label": "Close profile", p: "$2", rounded: "$2", hoverStyle: { bg: '$hover' }, children: _jsx(X, { size: 16, "aria-hidden": true }) })] }), _jsxs(YStack, { flex: 1, minH: 0, overflow: "scroll", px: "$4", pb: "$5", gap: "$4", children: [_jsx("input", { ref: picker, type: "file", accept: "image/png,image/jpeg,image/gif,image/webp", hidden: true, onChange: (e) => {
58
+ position: "absolute", t: 0, r: 0, b: 0, l: 0, z: "var(--z-panel)", $lg: { position: 'relative', z: 'var(--z-base)', l: 'auto' }, children: [_jsxs(XStack, { items: "center", px: "$4", py: "$3", gap: "$2", children: [_jsx(Text, { flex: 1, fontSize: "$5", fontWeight: "600", color: "$ink", children: "Profile" }), _jsx(Box, { render: "button", onClick: onClose, "aria-label": "Close profile", p: "$2", rounded: "$2", hoverStyle: { bg: '$hover' }, children: _jsx(X, { size: 16, "aria-hidden": true }) })] }), _jsxs(YStack, { flex: 1, minH: 0, overflowY: "auto", overflowX: "hidden", px: "$4", pb: "$5", gap: "$4", children: [_jsx("input", { ref: picker, type: "file", accept: "image/png,image/jpeg,image/gif,image/webp", hidden: true, onChange: (e) => {
59
59
  const file = e.target.files?.[0];
60
60
  e.target.value = '';
61
61
  void pick(file);
package/dist/RightPane.js CHANGED
@@ -152,7 +152,7 @@ export function RightPane({ said = [], onClose, }) {
152
152
  // having to tell it that the conversation changed.
153
153
  const at = channel(open);
154
154
  const pane = usePane(at);
155
- return (_jsxs(YStack, { flex: 1, minH: 0, bg: "$background", borderLeftWidth: 1, borderColor: "$borderColor", children: [pane.pinned ? _jsx(Summary, { at: at, said: said, held: pane.held }) : null, _jsx(Browser, { at: at, pane: pane, onClose: onClose })] }));
155
+ return (_jsxs(YStack, { flex: 1, minH: 320, bg: "$background", borderLeftWidth: 1, borderColor: "$borderColor", children: [pane.pinned ? _jsx(Summary, { at: at, said: said, held: pane.held }) : null, _jsx(Browser, { at: at, pane: pane, onClose: onClose })] }));
156
156
  }
157
157
  /** The two cards: out of the conversation, and into it. */
158
158
  function Summary({ at, said, held }) {
@@ -165,7 +165,7 @@ function Summary({ at, said, held }) {
165
165
  // it would take the browser's half, and scrolls after that — so two rows
166
166
  // do not reserve a screen and thirty do not push the browser off the
167
167
  // bottom of the column.
168
- maxH: "45%", overflow: "scroll", p: "$3", gap: "$3", borderBottomWidth: 1, borderColor: "$borderColor", children: [_jsx(Marks, { at: at, kind: "outputs", title: "Outputs", icon: _jsx(FileCode, { size: 16, "aria-hidden": true }), marks: made, children: "This conversation has produced nothing yet." }), _jsx(Marks, { at: at, kind: "sources", title: "Sources", icon: _jsx(Paperclip, { size: 16, "aria-hidden": true }), marks: took, children: "Nothing has been put into this conversation yet." })] }));
168
+ maxH: "45%", overflowY: "auto", overflowX: "hidden", p: "$3", gap: "$3", borderBottomWidth: 1, borderColor: "$borderColor", children: [_jsx(Marks, { at: at, kind: "outputs", title: "Outputs", icon: _jsx(FileCode, { size: 16, "aria-hidden": true }), marks: made, children: "This conversation has produced nothing yet." }), _jsx(Marks, { at: at, kind: "sources", title: "Sources", icon: _jsx(Paperclip, { size: 16, "aria-hidden": true }), marks: took, children: "Nothing has been put into this conversation yet." })] }));
169
169
  }
170
170
  /**
171
171
  * One card of things, the first {@link FEW} of them, and the honest number of
@@ -338,7 +338,7 @@ function Browser({ at, pane, onClose, }) {
338
338
  , {
339
339
  // 44 IS THE FLOOR A THUMB NEEDS, and the strip scrolls rather than
340
340
  // squeezing its tabs under it — the rule `tabs.tsx` exists to hold.
341
- minH: 44, shrink: 0, items: "center", gap: "$1", px: "$2", overflow: "scroll", borderBottomWidth: 1, borderColor: "$borderColor", children: [pane.tabs.map((t) => (_jsx(Sheet, { at: at, leaf: t, on: t.id === pane.at }, t.id))), _jsx(Box, { render: "button", onClick: () => openTab(at, 'https://hanzo.ai'), "aria-label": "New tab", p: "$1.5", rounded: "$2", shrink: 0, hoverStyle: { bg: '$hover' }, children: _jsx(Plus, { size: 16, "aria-hidden": true }) }), _jsx(Box, { flex: 1, minW: "$1" }), leaf ? (_jsxs(_Fragment, { children: [_jsx(Box, { render: "button", onClick: () => reload((n) => n + 1), "aria-label": "Reload this tab", p: "$1.5", rounded: "$2", shrink: 0, hoverStyle: { bg: '$hover' }, children: _jsx(RotateCw, { size: 16, "aria-hidden": true }) }), _jsx(Box, { render: "button", onClick: () => window.open(leaf.url, '_blank', 'noopener,noreferrer'), "aria-label": "Open this tab in a new window", p: "$1.5", rounded: "$2", shrink: 0, hoverStyle: { bg: '$hover' }, children: _jsx(ExternalLink, { size: 16, "aria-hidden": true }) })] })) : null, onClose ? (_jsx(Box, { render: "button", onClick: onClose, "aria-label": "Hide side panel", p: "$1.5", rounded: "$2", shrink: 0, hoverStyle: { bg: '$hover' }, children: _jsx(PanelRightClose, { size: 16, "aria-hidden": true }) })) : null] }), leaf ? _jsx(Where, { at: at, leaf: leaf }) : null, _jsx(YStack, { flex: 1, minH: 0, children: leaf ? (_jsx(Preview, { leaf: leaf, held: pane.held }, `${leaf.id}.${again}`)) : (_jsxs(YStack, { flex: 1, items: "center", justify: "center", p: "$5", gap: "$2", children: [_jsx(Quiet, { children: "Nothing open here." }), _jsx(Quiet, { children: "Press an output or a source to look at it, or open a tab." })] })) })] }));
341
+ minH: 44, shrink: 0, items: "center", gap: "$1", px: "$2", overflowX: "auto", overflowY: "hidden", borderBottomWidth: 1, borderColor: "$borderColor", children: [pane.tabs.map((t) => (_jsx(Sheet, { at: at, leaf: t, on: t.id === pane.at }, t.id))), _jsx(Box, { render: "button", onClick: () => openTab(at, 'https://hanzo.ai'), "aria-label": "New tab", p: "$1.5", rounded: "$2", shrink: 0, hoverStyle: { bg: '$hover' }, children: _jsx(Plus, { size: 16, "aria-hidden": true }) }), _jsx(Box, { flex: 1, minW: "$1" }), leaf ? (_jsxs(_Fragment, { children: [_jsx(Box, { render: "button", onClick: () => reload((n) => n + 1), "aria-label": "Reload this tab", p: "$1.5", rounded: "$2", shrink: 0, hoverStyle: { bg: '$hover' }, children: _jsx(RotateCw, { size: 16, "aria-hidden": true }) }), _jsx(Box, { render: "button", onClick: () => window.open(leaf.url, '_blank', 'noopener,noreferrer'), "aria-label": "Open this tab in a new window", p: "$1.5", rounded: "$2", shrink: 0, hoverStyle: { bg: '$hover' }, children: _jsx(ExternalLink, { size: 16, "aria-hidden": true }) })] })) : null, onClose ? (_jsx(Box, { render: "button", onClick: onClose, "aria-label": "Hide side panel", p: "$1.5", rounded: "$2", shrink: 0, hoverStyle: { bg: '$hover' }, children: _jsx(PanelRightClose, { size: 16, "aria-hidden": true }) })) : null] }), leaf ? _jsx(Where, { at: at, leaf: leaf }) : null, _jsx(YStack, { flex: 1, minH: 0, children: leaf ? (_jsx(Preview, { leaf: leaf, held: pane.held }, `${leaf.id}.${again}`)) : (_jsxs(YStack, { flex: 1, items: "center", justify: "center", p: "$5", gap: "$2", children: [_jsx(Quiet, { children: "Nothing open here." }), _jsx(Quiet, { children: "Press an output or a source to look at it, or open a tab." })] })) })] }));
342
342
  }
343
343
  /** One tab: the thing you press, and the cross that shuts it. */
344
344
  function Sheet({ at, leaf, on }) {
@@ -385,7 +385,7 @@ function Preview({ leaf, held }) {
385
385
  const type = mine?.type ?? '';
386
386
  const ext = suffix(mine?.name ?? leaf.title);
387
387
  if (type.startsWith('image/') || ['png', 'jpg', 'jpeg', 'gif', 'webp', 'avif', 'svg'].includes(ext)) {
388
- return (_jsx(YStack, { flex: 1, minH: 0, items: "center", justify: "center", p: "$3", overflow: "scroll", children: _jsx(View, { render: _jsx("img", { src: leaf.url, alt: leaf.title }), maxW: "100%", maxH: "100%", objectFit: "contain" }) }));
388
+ return (_jsx(YStack, { flex: 1, minH: 0, items: "center", justify: "center", p: "$3", overflowY: "auto", overflowX: "hidden", children: _jsx(View, { render: _jsx("img", { src: leaf.url, alt: leaf.title }), maxW: "100%", maxH: "100%", objectFit: "contain" }) }));
389
389
  }
390
390
  // THE LIST IS OF WHAT A FRAME CAN RENDER, not of what a file might be. A type
391
391
  // absent from it is unsupported, which is the honest default: guessing that
package/dist/Settings.js CHANGED
@@ -139,7 +139,7 @@ export function SettingsList({ active, filter, onPick, onBack, }) {
139
139
  */
140
140
  export function SettingsPane({ id }) {
141
141
  const row = ROWS.find((r) => r.id === id);
142
- return (_jsx(YStack, { flex: 1, minH: 0, overflow: "scroll", children: _jsxs(YStack, { p: "$5", gap: "$4", maxW: 860, children: [_jsx(Text, { fontSize: "$6", fontWeight: "600", color: "$ink", children: row?.label ?? 'Settings' }), _jsx(Pane, { id: id, note: row?.note })] }) }));
142
+ return (_jsx(YStack, { flex: 1, minH: 0, overflowY: "auto", overflowX: "hidden", children: _jsxs(YStack, { p: "$5", gap: "$4", maxW: 860, children: [_jsx(Text, { fontSize: "$6", fontWeight: "600", color: "$ink", children: row?.label ?? 'Settings' }), _jsx(Pane, { id: id, note: row?.note })] }) }));
143
143
  }
144
144
  /** One pane. */
145
145
  function Pane({ id, note }) {
package/dist/Shell.js CHANGED
@@ -887,9 +887,13 @@ export function Paned({ children, swap, framed = false, bare = false, }) {
887
887
  // In the frame the gutter stands between the pane and this column.
888
888
  ml: framed ? "var(--pane-gap)" : undefined, ref: beside.ref,
889
889
  // inline-style: `--span`, the width a drag rewrites on the element itself (span.ts); no prop sets a variable.
890
- style: beside.style, position: "relative", width: "var(--span)", maxW: "50%", shrink: 0, minH: 0, display: "none", $lg: { display: "flex" }, children: [_jsx(Grip, { side: "right", span: beside.span, floor: FLOOR, ceil: CEIL, reset: BESIDE_WIDTH, onSpan: beside.move, onKeep: beside.keep, onShut: () => showAside(null), label: "Resize side panel",
890
+ style: beside.style, position: "relative", width: "var(--span)",
891
+ // Half the WINDOW. Half of an `auto` grid track is half of this
892
+ // column's own width, so the rooms' shell drew it at half its span
893
+ // beside an empty strip of the same size.
894
+ maxW: "50vw", shrink: 0, minH: 0, display: "none", $lg: { display: "flex" }, children: [_jsx(Grip, { side: "right", span: beside.span, floor: FLOOR, ceil: CEIL, reset: BESIDE_WIDTH, onSpan: beside.move, onKeep: beside.keep, onShut: () => showAside(null), label: "Resize side panel",
891
895
  // In the gutter the frame leaves between the panes; a room has none.
892
- l: GUTTER_START, width: GUTTER }), _jsx(YStack, { flex: 1, minH: 0, overflow: "scroll", rounded: "var(--pane-round)", ref: (el) => setSlot(el instanceof HTMLElement ? el : null) })] })) : null] }));
896
+ l: GUTTER_START, width: GUTTER }), _jsx(YStack, { flex: 1, minH: 0, overflowY: "auto", overflowX: "hidden", rounded: "var(--pane-round)", ref: (el) => setSlot(el instanceof HTMLElement ? el : null) })] })) : null] }));
893
897
  }
894
898
  /**
895
899
  * THE RAIL: where you can go, as marks with their names under them. It is the
package/dist/Talk.js CHANGED
@@ -77,7 +77,7 @@ export function Talk({ room }) {
77
77
  }
78
78
  };
79
79
  const name = (of) => setDraft((held) => mention(held, of));
80
- return (_jsxs(YStack, { flex: 1, minH: 0, children: [_jsxs(ChannelHeader, { name: titleOf(room), children: [room.topic ? (_jsx(Text, { fontSize: "$2", color: "$soft", numberOfLines: 1, children: room.topic })) : null, _jsx(Box, { render: "button", "aria-label": aside === 'roster' ? 'Hide directory & roster' : 'Show directory & roster', "aria-pressed": aside === 'roster', onClick: () => showAside(aside === 'roster' ? null : 'roster'), ml: "auto", p: "$2", rounded: "$3", hoverStyle: { bg: '$hover' }, children: _jsx(Users, { size: 16, "aria-hidden": true }) })] }), _jsxs(YStack, { flex: 1, minH: 0, overflow: "scroll", p: "$4", gap: "$3", children: [failed ? (_jsx(Text, { fontSize: "$3", color: "$soft", children: say(failed, 'this room') })) : messages === null ? (_jsx(Text, { fontSize: "$3", color: "$soft", children: "Reading the room." })) : messages.length === 0 && pending.length === 0 ? (_jsx(Text, { fontSize: "$3", color: "$soft", children: "Nothing has been said here yet." })) : (messages.map((one) => (_jsx(Line, { member: memberOf(members, one.author), fallback: one.author, stamp: when(one.createdOn), text: one.text }, one.id)))), pending
80
+ return (_jsxs(YStack, { flex: 1, minH: 0, children: [_jsxs(ChannelHeader, { name: titleOf(room), children: [room.topic ? (_jsx(Text, { fontSize: "$2", color: "$soft", numberOfLines: 1, children: room.topic })) : null, _jsx(Box, { render: "button", "aria-label": aside === 'roster' ? 'Hide directory & roster' : 'Show directory & roster', "aria-pressed": aside === 'roster', onClick: () => showAside(aside === 'roster' ? null : 'roster'), ml: "auto", p: "$2", rounded: "$3", hoverStyle: { bg: '$hover' }, children: _jsx(Users, { size: 16, "aria-hidden": true }) })] }), _jsxs(YStack, { flex: 1, minH: 0, overflowY: "auto", overflowX: "hidden", p: "$4", gap: "$3", children: [failed ? (_jsx(Text, { fontSize: "$3", color: "$soft", children: say(failed, 'this room') })) : messages === null ? (_jsx(Text, { fontSize: "$3", color: "$soft", children: "Reading the room." })) : messages.length === 0 && pending.length === 0 ? (_jsx(Text, { fontSize: "$3", color: "$soft", children: "Nothing has been said here yet." })) : (messages.map((one) => (_jsx(Line, { member: memberOf(members, one.author), fallback: one.author, stamp: when(one.createdOn), text: one.text }, one.id)))), pending
81
81
  .filter((one) => !messages?.some((said) => said.author === me && said.text === one.text))
82
82
  .map((one) => (_jsx(Line, { member: mine, fallback: "You", text: one.text, soft: true }, `pending-${one.key}`)))] }), _jsxs(YStack, { p: "$3", gap: "$2", borderTopWidth: 1, borderColor: "$borderColor", children: [_jsx(Composer, { value: draft, onChange: setDraft, onSend: () => void speak(), placeholder: `Message ${titleOf(room)}`, label: `Message ${titleOf(room)}`, hint: agents.length ? 'Mention an agent with @name and it answers.' : undefined, children: agents.length ? (_jsx(XStack, { gap: "$1", flexWrap: "wrap", shrink: 1, minW: 0, children: agents.map((one) => (_jsx(Box, { render: "button", "aria-label": `Mention ${one.name}`, onClick: () => name(one.name), px: "$2", py: "$1", rounded: "$10", hoverStyle: { bg: '$hover' }, children: _jsxs(Text, { fontSize: "$2", color: "$soft", children: ["@", one.name] }) }, one.id))) })) : null }), refused ? (_jsx(Text, { fontSize: "$2", color: BAD, children: refused })) : null] }), _jsx(Beside, { children: aside === 'roster' ? _jsx(Roster, { members: members, wrong: wrong, onMention: name }) : null })] }));
83
83
  }
package/dist/Vibe.js CHANGED
@@ -166,7 +166,7 @@ function Vibe() {
166
166
  .catch((e) => setTurns((prev) => prev.map((t) => (t.id === id ? { ...t, refused: plainly(e, 'this run') } : t))))
167
167
  .finally(() => setBusy(false));
168
168
  };
169
- 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, overflow: "scroll", 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
169
+ 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
170
170
  ? `Running on ${turn.run.targetId}`
171
171
  : '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: [_jsxs(Picker, { value: model, onChange: (e) => setWanted(e.target.value), "aria-label": "Model", maxW: 200, children: [models.length === 0 ? _jsx("option", { value: model, children: model }) : null, models.map((m) => (_jsx("option", { value: m.id, children: m.id }, m.id)))] }), _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) => {
172
172
  if (e.key === 'Enter' && !e.shiftKey) {
@@ -177,7 +177,7 @@ function Vibe() {
177
177
  ['desktop', 'Desktop', Monitor],
178
178
  ['tablet', 'Tablet', Tablet],
179
179
  ['mobile', 'Mobile', Smartphone],
180
- ].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, overflow: "scroll", p: "$3", gap: "$3", children: _jsx(Team, {}) })] })] })] }));
180
+ ].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, {}) })] })] })] }));
181
181
  }
182
182
  // THE GATE IS NOT A ROOM'S TO REMEMBER. This room draws its own shell rather
183
183
  // than `Room`, which is where every other room picks the organization gate up —
package/dist/Work.js CHANGED
@@ -157,7 +157,7 @@ function Watching({ idle }) {
157
157
  */
158
158
  function Idle({ runs }) {
159
159
  if (runs === 0) {
160
- return (_jsx(YStack, { flex: 1, minH: 0, overflow: "scroll", children: _jsxs(YStack, { width: "100%", maxW: 640, mx: "auto", px: "$5", pt: "$10", gap: "$4", children: [_jsxs(YStack, { gap: "$2", children: [_jsx(Text, { render: "h1", fontSize: "$7", fontWeight: "500", color: "$ink", children: "Start a run" }), _jsx(Text, { fontSize: "$3", color: "$soft", maxW: 520, children: "Give an agent a task. Its plan, progress, and output will appear here." })] }), _jsx(Start, {}), _jsx(Text, { fontSize: "$1", color: "$soft", children: "Runs stay visible in Working so you can inspect or steer them at any time." })] }) }));
160
+ return (_jsx(YStack, { flex: 1, minH: 0, overflowY: "auto", overflowX: "hidden", children: _jsxs(YStack, { width: "100%", maxW: 640, mx: "auto", px: "$5", pt: "$10", gap: "$4", children: [_jsxs(YStack, { gap: "$2", children: [_jsx(Text, { render: "h1", fontSize: "$7", fontWeight: "500", color: "$ink", children: "Start a run" }), _jsx(Text, { fontSize: "$3", color: "$soft", maxW: 520, children: "Give an agent a task. Its plan, progress, and output will appear here." })] }), _jsx(Start, {}), _jsx(Text, { fontSize: "$1", color: "$soft", children: "Runs stay visible in Working so you can inspect or steer them at any time." })] }) }));
161
161
  }
162
162
  return (_jsx(YStack, { flex: 1, items: "center", justify: "center", p: "$6", children: _jsx(Text, { fontSize: "$3", color: "$soft", children: "Pick a run to watch it work." }) }));
163
163
  }
@@ -230,7 +230,7 @@ function Watch({ id }) {
230
230
  setNote('Could not reach this run');
231
231
  }
232
232
  }, [ai, draft, id]);
233
- return (_jsxs(XStack, { flex: 1, minH: 0, flexWrap: "wrap", children: [_jsxs(YStack, { flex: 1, minH: 0, minW: 0, children: [_jsx(ChannelHeader, { name: session?.title || session?.agent || id, artifacts: 0 }), _jsx(XStack, { px: "$4", pb: "$2", children: _jsx(Bar, { run: session }) }), _jsx(YStack, { flex: 1, minH: 0, overflow: "scroll", p: "$4", gap: "$4", children: _jsx(ProgressBlock, { steps: shown, done: finished, children: message ? (_jsx(Text, { fontSize: "$4", color: "$ink", children: message })) : null }) }), _jsxs(YStack, { p: "$3", gap: "$2", borderTopWidth: 1, borderColor: "$borderColor", children: [_jsx(Steer, { onCommand: (c) => void steer(c), withhold: finished ? ['pause', 'resume', 'stop'] : [], note: note }), _jsx(Composer, { value: draft, onChange: setDraft, onSend: () => void send(), placeholder: "Steer this run\u2026", label: "Steer this run" })] })] }), _jsx(YStack, { width: "100%", p: "$3", borderTopWidth: 1, $sm: { width: 420, shrink: 0, borderTopWidth: 0, borderLeftWidth: 1 }, borderColor: "$borderColor", children: _jsx(Pane, { tabs: [
233
+ return (_jsxs(XStack, { flex: 1, minH: 0, flexWrap: "wrap", children: [_jsxs(YStack, { flex: 1, minH: 0, minW: 0, children: [_jsx(ChannelHeader, { name: session?.title || session?.agent || id, artifacts: 0 }), _jsx(XStack, { px: "$4", pb: "$2", children: _jsx(Bar, { run: session }) }), _jsx(YStack, { flex: 1, minH: 0, overflowY: "auto", overflowX: "hidden", p: "$4", gap: "$4", children: _jsx(ProgressBlock, { steps: shown, done: finished, children: message ? (_jsx(Text, { fontSize: "$4", color: "$ink", children: message })) : null }) }), _jsxs(YStack, { p: "$3", gap: "$2", borderTopWidth: 1, borderColor: "$borderColor", children: [_jsx(Steer, { onCommand: (c) => void steer(c), withhold: finished ? ['pause', 'resume', 'stop'] : [], note: note }), _jsx(Composer, { value: draft, onChange: setDraft, onSend: () => void send(), placeholder: "Steer this run\u2026", label: "Steer this run" })] })] }), _jsx(YStack, { width: "100%", p: "$3", borderTopWidth: 1, $sm: { width: 420, shrink: 0, borderTopWidth: 0, borderLeftWidth: 1 }, borderColor: "$borderColor", children: _jsx(Pane, { tabs: [
234
234
  { id: 'code', label: 'Code', kind: 'code', content: _jsx(Nothing, { what: "changes" }) },
235
235
  { id: 'view', label: 'View', kind: 'view', content: _jsx(Nothing, { what: "preview" }) },
236
236
  { id: 'canvas', label: 'Canvas', kind: 'canvas', content: _jsx(Nothing, { what: "summary" }) },
package/dist/crew.js CHANGED
@@ -98,11 +98,11 @@ export function Crew({ to } = {}) {
98
98
  if (here !== to)
99
99
  router.push(to);
100
100
  };
101
- return (_jsxs(_Fragment, { children: [_jsxs(XStack, { items: "flex-start", flexWrap: "nowrap", overflowX: "auto", gap: "$2.5", px: "$4", py: "$3", width: "100%", maxW: 820, mx: "auto", "data-slot": "crew", ...bare, role: "group", "aria-label": "Agents", children: [_jsx(Box, { render: "button", onClick: () => setHiring(true), "aria-label": "New agent", "aria-haspopup": "dialog", shrink: 0, children: _jsxs(YStack, { items: "center", gap: "$1.5", width: 68, children: [_jsx(YStack, { width: DISC, height: DISC, rounded: DISC / 2, borderWidth: 1.5, borderStyle: "dashed", borderColor: "$soft", items: "center", justify: "center", hoverStyle: { borderColor: '$ink', bg: '$raised' }, children: _jsx(Plus, { size: 18, "aria-hidden": true }) }), _jsx(Text, { fontSize: "$1", color: "$soft", numberOfLines: 1, children: "New" })] }) }), ordered(agents).map((one) => {
101
+ return (_jsxs(_Fragment, { children: [_jsxs(XStack, { items: "flex-start", flexWrap: "nowrap", overflowX: "auto", overflowY: "hidden", gap: "$2.5", px: "$4", py: "$3", width: "100%", maxW: 820, mx: "auto", "data-slot": "crew", ...bare, role: "group", "aria-label": "Agents", children: [ordered(agents).map((one) => {
102
102
  const name = called(one.name);
103
103
  const on = room.some((there) => there.toLowerCase() === one.name.toLowerCase());
104
104
  return (_jsx(Box, { render: "button", onClick: () => talk(name), "aria-pressed": on, "aria-label": `Chat with ${name}`, title: to ? `Chat with ${name}` : on ? `Remove ${name} from this chat` : `Add ${name} to this chat`, shrink: 0, children: _jsxs(YStack, { items: "center", gap: "$1.5", width: 68, children: [_jsxs(YStack, { position: "relative", rounded: DISC / 2 + 2, p: 2, borderWidth: 1.5, borderColor: on ? '$ink' : 'transparent', hoverStyle: { borderColor: on ? '$ink' : '$borderColor' }, children: [_jsx(Face, { src: one.avatar, emoji: one.emoji, name: one.name, size: DISC }), on ? (_jsx(YStack, { "aria-hidden": true, position: "absolute", b: -2, r: -2, width: 18, height: 18, rounded: 9, bg: "$ink", borderWidth: 2, borderColor: "$background", items: "center", justify: "center", children: _jsx(Check, { size: 10, strokeWidth: 3, color: "var(--background)" }) })) : null] }), _jsx(Text, { fontSize: "$1", color: on ? '$ink' : '$soft', numberOfLines: 1, maxW: 68, children: name })] }) }, one.id || one.name));
105
- })] }), hiring ? _jsx(Hire, { onClose: () => setHiring(false), onMade: talk }) : null] }));
105
+ }), _jsx(Box, { render: "button", onClick: () => setHiring(true), "aria-label": "New agent", "aria-haspopup": "dialog", shrink: 0, children: _jsxs(YStack, { items: "center", gap: "$1.5", width: 68, children: [_jsx(YStack, { width: DISC, height: DISC, rounded: DISC / 2, borderWidth: 1.5, borderStyle: "dashed", borderColor: "$soft", items: "center", justify: "center", hoverStyle: { borderColor: '$ink', bg: '$raised' }, children: _jsx(Plus, { size: 18, "aria-hidden": true }) }), _jsx(Text, { fontSize: "$1", color: "$soft", numberOfLines: 1, children: "New" })] }) })] }), hiring ? _jsx(Hire, { onClose: () => setHiring(false), onMade: talk }) : null] }));
106
106
  }
107
107
  /** The agents API's name rule (cloud apps/agents `nameRE`), checked before the request. */
108
108
  const NAME = /^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$/;
package/dist/ground.d.ts CHANGED
@@ -2,8 +2,10 @@
2
2
  * The ground the workspace's cards stand on, and how each card is cut.
3
3
  *
4
4
  * A PANE is a card floating on the ground, cut by the `--pane-*` tokens
5
- * (app/globals.css): a corner, an edge, the lens behind it, and the second
6
- * paper rung's lit edge and drop. The fill is the caller's rung: `--pane-fill`
5
+ * (@hanzo/design elevation.css): a corner, an edge, the lens behind it, and the
6
+ * first paper rung's lit edge and drop — the second's 15px drop and the floor's
7
+ * strong glass read as a gradient through a translucent pane, so both stay one
8
+ * rung down. The fill is the caller's rung: `--pane-fill`
7
9
  * in the app's frame, `sheet(1)`/`sheet(2)` where a pane lies in a room.
8
10
  *
9
11
  * The drop is a prop and not `sheet(2)`'s `elevation-2` class, whose shadow is
@@ -16,7 +18,7 @@ export declare const pane: (fill?: string) => {
16
18
  readonly borderWidth: 1;
17
19
  readonly borderColor: 'var(--pane-edge)';
18
20
  readonly backdropFilter: 'var(--pane-blur)';
19
- readonly boxShadow: 'var(--shadow-sheet-2)';
21
+ readonly boxShadow: 'var(--shadow-sheet-1)';
20
22
  };
21
23
  /**
22
24
  * THE FLOOR under the app's frame (app/_web.tsx, components/settings/Frame.tsx):
@@ -26,7 +28,7 @@ export declare const pane: (fill?: string) => {
26
28
  */
27
29
  export declare const floor: {
28
30
  readonly backgroundColor: 'var(--pane-floor)';
29
- readonly backgroundImage: 'linear-gradient(to bottom, var(--glass-strong), transparent), var(--pane-ground)';
31
+ readonly backgroundImage: 'linear-gradient(to bottom, var(--glass), transparent), var(--pane-ground)';
30
32
  readonly backgroundSize: '100% 70%, auto';
31
33
  readonly backgroundRepeat: 'no-repeat';
32
34
  };
package/dist/ground.js CHANGED
@@ -2,8 +2,10 @@
2
2
  * The ground the workspace's cards stand on, and how each card is cut.
3
3
  *
4
4
  * A PANE is a card floating on the ground, cut by the `--pane-*` tokens
5
- * (app/globals.css): a corner, an edge, the lens behind it, and the second
6
- * paper rung's lit edge and drop. The fill is the caller's rung: `--pane-fill`
5
+ * (@hanzo/design elevation.css): a corner, an edge, the lens behind it, and the
6
+ * first paper rung's lit edge and drop — the second's 15px drop and the floor's
7
+ * strong glass read as a gradient through a translucent pane, so both stay one
8
+ * rung down. The fill is the caller's rung: `--pane-fill`
7
9
  * in the app's frame, `sheet(1)`/`sheet(2)` where a pane lies in a room.
8
10
  *
9
11
  * The drop is a prop and not `sheet(2)`'s `elevation-2` class, whose shadow is
@@ -16,7 +18,7 @@ export const pane = (fill = 'var(--pane-fill)') => ({
16
18
  borderWidth: 1,
17
19
  borderColor: 'var(--pane-edge)',
18
20
  backdropFilter: 'var(--pane-blur)',
19
- boxShadow: 'var(--shadow-sheet-2)',
21
+ boxShadow: 'var(--shadow-sheet-1)',
20
22
  });
21
23
  /**
22
24
  * THE FLOOR under the app's frame (app/_web.tsx, components/settings/Frame.tsx):
@@ -26,7 +28,7 @@ export const pane = (fill = 'var(--pane-fill)') => ({
26
28
  */
27
29
  export const floor = {
28
30
  backgroundColor: 'var(--pane-floor)',
29
- backgroundImage: 'linear-gradient(to bottom, var(--glass-strong), transparent), var(--pane-ground)',
31
+ backgroundImage: 'linear-gradient(to bottom, var(--glass), transparent), var(--pane-ground)',
30
32
  backgroundSize: '100% 70%, auto',
31
33
  backgroundRepeat: 'no-repeat',
32
34
  };
@@ -0,0 +1,38 @@
1
+ /**
2
+ * Files that go with a chat message.
3
+ *
4
+ * @hanzo/ai's `send(text, images)` carries a picture as an `image_url` part the
5
+ * model reads, and nothing else rides that wire, so a file goes one of two
6
+ * ways: an image as a `data:` URI beside the words, a text file as its own
7
+ * words after the message, fenced under its name. Any other kind is refused by
8
+ * name, before anything is sent.
9
+ */
10
+ import type { MessagePart } from '@hanzo/ui/chat';
11
+ /** A file held for the next message. */
12
+ export interface Held {
13
+ id: string;
14
+ name: string;
15
+ kind: 'image' | 'text';
16
+ /** A `data:` URI for an image, the words for a text file. */
17
+ data: string;
18
+ size: number;
19
+ }
20
+ export declare const IMAGE_MAX: number;
21
+ export declare const TEXT_MAX: number;
22
+ /** Which way a file goes with a message, or null when it cannot go. */
23
+ export declare function kindOf(file: {
24
+ name: string;
25
+ type: string;
26
+ }): Held['kind'] | null;
27
+ /** A file read and held, or the sentence that says why it cannot go. */
28
+ export declare function hold(file: File): Promise<Held | string>;
29
+ /**
30
+ * The message as sent: the words, then each text file fenced under its name.
31
+ * With no words and no text file, the images' names, so the turn says what it
32
+ * carries.
33
+ */
34
+ export declare function compose(text: string, files: readonly Held[]): string;
35
+ /** The images of a message, as the URIs @hanzo/ai's `send` takes. */
36
+ export declare const images: (files: readonly Held[]) => string[];
37
+ /** A wire turn's parts — text and `image_url` — as the parts `@hanzo/ui/chat` draws. */
38
+ export declare function partsOf(content: readonly unknown[]): MessagePart[];
@@ -0,0 +1,82 @@
1
+ /**
2
+ * Files that go with a chat message.
3
+ *
4
+ * @hanzo/ai's `send(text, images)` carries a picture as an `image_url` part the
5
+ * model reads, and nothing else rides that wire, so a file goes one of two
6
+ * ways: an image as a `data:` URI beside the words, a text file as its own
7
+ * words after the message, fenced under its name. Any other kind is refused by
8
+ * name, before anything is sent.
9
+ */
10
+ export const IMAGE_MAX = 8 * 1024 * 1024;
11
+ export const TEXT_MAX = 256 * 1024;
12
+ const TEXT_TYPE = /^(text\/|application\/(json|ld\+json|xml|x-yaml|yaml|toml|javascript|typescript|x-sh|sql|x-tex))/;
13
+ const TEXT_NAME = /\.(md|mdx|txt|csv|tsv|json|jsonl|ya?ml|toml|xml|html?|css|scss|[cm]?[jt]sx?|py|go|rs|rb|java|kt|swift|c|cc|cpp|h|hpp|cs|php|sh|bash|zsh|sql|ini|env|log|tex|proto|graphql)$/i;
14
+ /** Which way a file goes with a message, or null when it cannot go. */
15
+ export function kindOf(file) {
16
+ if (file.type.startsWith('image/'))
17
+ return 'image';
18
+ if (TEXT_TYPE.test(file.type) || TEXT_NAME.test(file.name))
19
+ return 'text';
20
+ return null;
21
+ }
22
+ function read(file, as) {
23
+ return new Promise((resolve, reject) => {
24
+ const reader = new FileReader();
25
+ reader.onload = () => resolve(String(reader.result ?? ''));
26
+ reader.onerror = () => reject(reader.error ?? new Error(`${file.name} could not be read.`));
27
+ if (as === 'url')
28
+ reader.readAsDataURL(file);
29
+ else
30
+ reader.readAsText(file);
31
+ });
32
+ }
33
+ /** A file read and held, or the sentence that says why it cannot go. */
34
+ export async function hold(file) {
35
+ const kind = kindOf(file);
36
+ if (!kind)
37
+ return `${file.name}: only images and text files go with a message.`;
38
+ const max = kind === 'image' ? IMAGE_MAX : TEXT_MAX;
39
+ if (file.size > max)
40
+ return `${file.name} is larger than ${kind === 'image' ? '8 MB' : '256 KB'}.`;
41
+ try {
42
+ const data = await read(file, kind === 'image' ? 'url' : 'text');
43
+ return { id: crypto.randomUUID(), name: file.name, kind, data, size: file.size };
44
+ }
45
+ catch {
46
+ return `${file.name} could not be read.`;
47
+ }
48
+ }
49
+ /** A fence longer than any run of backticks inside the words, so they cannot close it. */
50
+ function fence(words) {
51
+ const longest = Math.max(0, ...(words.match(/`+/g) ?? []).map((run) => run.length));
52
+ return '`'.repeat(Math.max(3, longest + 1));
53
+ }
54
+ /**
55
+ * The message as sent: the words, then each text file fenced under its name.
56
+ * With no words and no text file, the images' names, so the turn says what it
57
+ * carries.
58
+ */
59
+ export function compose(text, files) {
60
+ const blocks = files
61
+ .filter((f) => f.kind === 'text')
62
+ .map((f) => {
63
+ const f3 = fence(f.data);
64
+ return `${f.name}\n${f3}\n${f.data.replace(/\n$/, '')}\n${f3}`;
65
+ });
66
+ const out = [text.trim(), ...blocks].filter(Boolean).join('\n\n');
67
+ return out || files.map((f) => f.name).join(', ');
68
+ }
69
+ /** The images of a message, as the URIs @hanzo/ai's `send` takes. */
70
+ export const images = (files) => files.filter((f) => f.kind === 'image').map((f) => f.data);
71
+ /** A wire turn's parts — text and `image_url` — as the parts `@hanzo/ui/chat` draws. */
72
+ export function partsOf(content) {
73
+ const out = [];
74
+ for (const one of content) {
75
+ const part = one;
76
+ if (part?.type === 'text' && part.text)
77
+ out.push({ type: 'text', text: part.text });
78
+ else if (part?.type === 'image_url' && part.image_url?.url)
79
+ out.push({ type: 'image', url: part.image_url.url, alt: 'Attached image' });
80
+ }
81
+ return out;
82
+ }
package/dist/lib/tiers.js CHANGED
@@ -33,7 +33,7 @@
33
33
  import { useEffect, useState } from 'react';
34
34
  import { useIam, useOrganizations } from '@hanzo/iam/react';
35
35
  import { api } from './api.js';
36
- import { superAdmin as isSuperAdmin } from './session.js';
36
+ import { scope, superAdmin as isSuperAdmin } from './session.js';
37
37
  /** Every tier, for the callers that are entitled to all of them. */
38
38
  const ALL = ['alpha', 'beta'];
39
39
  /**
@@ -81,13 +81,13 @@ export function useTiers() {
81
81
  }
82
82
  const ac = new AbortController();
83
83
  // The PostHog-shaped evaluate: a distinct_id and the person's org, answered
84
- // with `{ featureFlags: { … } }`. The session cookie carries the principal;
85
- // with none the cloud answers 403 and this falls to the empty set, which is
86
- // the signed-out room — the safe direction.
84
+ // with `{ featureFlags: { … } }`. The bearer carries the principal, as on
85
+ // every other read: no host the rooms are drawn on holds a session cookie
86
+ // for the API, so a cookie alone was answered 403 on every page. A refusal
87
+ // still falls to the empty set, the signed-out room — the safe direction.
87
88
  fetch(`${api()}/v1/flags`, {
88
89
  method: 'POST',
89
- credentials: 'include',
90
- headers: { 'content-type': 'application/json' },
90
+ headers: { 'content-type': 'application/json', ...scope() },
91
91
  body: JSON.stringify({ distinct_id: id, person_properties: { org: organizations[0]?.name } }),
92
92
  signal: ac.signal,
93
93
  })
package/dist/tabs.js CHANGED
@@ -13,7 +13,7 @@ import { Box, Text, XStack } from '@hanzo/ui';
13
13
  * `after` is what sits past the last tab: the plus that would add one.
14
14
  */
15
15
  export function Tabs({ tabs, chosen, onPick, after, }) {
16
- return (_jsxs(XStack, { minH: 48, shrink: 0, items: "center", px: "$3", gap: "$3", overflow: "scroll", borderBottomWidth: 1, borderColor: "$borderColor", children: [tabs.map((one) => {
16
+ return (_jsxs(XStack, { minH: 48, shrink: 0, items: "center", px: "$3", gap: "$3", overflowX: "auto", overflowY: "hidden", borderBottomWidth: 1, borderColor: "$borderColor", children: [tabs.map((one) => {
17
17
  const on = one.id === chosen;
18
18
  const Icon = one.icon;
19
19
  return (_jsx(Box, { ...(onPick ? { render: 'button', onClick: () => onPick(one.id) } : {}), "aria-current": on,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hanzo/rooms",
3
- "version": "0.1.1",
3
+ "version": "0.1.3",
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",
@@ -45,7 +45,7 @@
45
45
  "simple-icons": "16.30.0"
46
46
  },
47
47
  "peerDependencies": {
48
- "@hanzo/ai": ">=0.6.17",
48
+ "@hanzo/ai": ">=0.6.20",
49
49
  "@hanzo/appearance": ">=0.2.6",
50
50
  "@hanzo/build": ">=0.2.34",
51
51
  "@hanzo/composer": ">=0.2.0",
@@ -63,7 +63,7 @@
63
63
  "react-dom": ">=19.2.8"
64
64
  },
65
65
  "devDependencies": {
66
- "@hanzo/ai": "0.6.17",
66
+ "@hanzo/ai": "0.6.20",
67
67
  "@hanzo/appearance": "0.2.6",
68
68
  "@hanzo/build": "0.2.34",
69
69
  "@hanzo/composer": "0.2.0",