@hanzo/rooms 0.1.2 → 0.1.4

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,22 +32,23 @@ 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
- import { SpeakButton, speakAgent } from "./speech.js";
38
+ import { SpeakButton, cleanForSpeech } from "./speech.js";
38
39
  import { Crew, HOUSE, useCrew } from "./crew.js";
39
40
  import { called } from "./team.js";
40
41
  import { Anchor, Box, Button, Text, View, XStack, YStack } from "@hanzo/ui";
41
42
  import { Control } from "@hanzo/composer";
42
43
  import { useIam, useIamToken } from "@hanzo/iam/react";
43
44
  import { hasSession, org } from "./lib/session.js";
44
- import { clock, sku, useLimits } from "./lib/limits.js";
45
+ import { chats, clock, sku, useLimits } from "./lib/limits.js";
45
46
  import { Meters } from "./meters.js";
46
47
  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");
@@ -430,7 +431,11 @@ function Thread({ placeholder = MESSAGE, initial, anonymous = false, heading, })
430
431
  // bare "not found", after it has already recorded the question. `served`
431
432
  // keeps the house name where it is served and otherwise falls to the first
432
433
  // model that is, so a room on any catalogue can be spoken to.
433
- const { models } = useModels();
434
+ // A SPEECH MODEL NEVER ANSWERS A CHAT TURN. The catalog lists the
435
+ // transcriber and the voice beside the chat models; one that names its
436
+ // outputs and names no text (`chats`) is neither offered nor fallen back to.
437
+ const { models: catalog } = useModels();
438
+ const models = useMemo(() => catalog.filter(chats), [catalog]);
434
439
  // HANZO MODELS ALONE are offered here, to everyone: an Enso or Zen id
435
440
  // (`sku`). A preference kept from before for any other model is not one this
436
441
  // room offers, so the house router is asked for instead, and `served` keeps
@@ -588,49 +593,66 @@ function Thread({ placeholder = MESSAGE, initial, anonymous = false, heading, })
588
593
  // identity changes on every `setMessages` — `useChat` closes over `messages` —
589
594
  // so an effect depending on it re-runs after each streamed token.
590
595
  const latest = useRef(send);
591
- latest.current = send;
596
+ // The images held for a turn ride with it at the moment it is sent, queued or
597
+ // not, and are spent once: a retry is the words again, not the pictures.
598
+ latest.current = (text) => send(text, outgoing.current.splice(0));
592
599
  // THE COMPOSER'S DRAFT IS HELD HERE, so an arriving question can be typed into
593
600
  // it. `Chat` owns a draft of its own and spreads `composer` after it, so
594
601
  // passing value/onChange/onSend takes that ownership over rather than fighting
595
602
  // it.
596
603
  const [draft, setDraft] = useState("");
604
+ // FILES WITH THE NEXT MESSAGE: pressed in through the paperclip, or dropped
605
+ // anywhere on the room. An image rides as a picture the model reads, a text
606
+ // file as its words (lib/attach); anything else is refused by name here.
607
+ const [files, setFiles] = useState([]);
608
+ const [fileSaid, setFileSaid] = useState(null);
609
+ const [dragging, setDragging] = useState(false);
610
+ const picker = useRef(null);
611
+ const outgoing = useRef([]);
612
+ const depth = useRef(0);
613
+ const take = useCallback(async (list) => {
614
+ if (!list?.length)
615
+ return;
616
+ const out = await Promise.all(Array.from(list).map(hold));
617
+ setFiles((now) => [...now, ...out.filter((one) => typeof one !== "string")]);
618
+ const refused = out.filter((one) => typeof one === "string");
619
+ setFileSaid(refused.length ? refused.join(" ") : null);
620
+ }, []);
597
621
  useSeed(initial, setDraft);
598
- // DICTATION, the same one the site's own composer offers. `speech()` defaults
599
- // to the Hanzo gateway and `base()` is the one place a host is read from, so
622
+ // DICTATION through Hanzo's own ear: the mic records and each pause is
623
+ // posted to /v1/audio/transcriptions (`zen-scribe`). `speech()` defaults to
624
+ // the Hanzo gateway and `base()` is the one place a host is read from, so
600
625
  // pointing a local cloud at one points both. The token is the reader's own —
601
- // the package never invents a credential — and where a browser has no
602
- // recogniser `<Voice/>` wears the reason rather than failing silently.
626
+ // the package never invents a credential — and a refusal is worn by
627
+ // `<Voice/>`, never passed off as a transcript.
603
628
  const { token } = useIamToken();
604
- const agentVoice = voiceOf(withWhom);
605
- const ear = useMemo(() => speech({
606
- ...base(),
607
- ...(token ? { token } : {}),
608
- voice: { name: agentVoice },
609
- }), [token, agentVoice]);
629
+ const ear = useMemo(() => speech({ ...base(), ...(token ? { token } : {}) }), [token]);
610
630
  const voice = useVoice({
611
631
  speech: ear,
612
632
  onPartial: setDraft,
613
633
  onUtterance: setDraft,
614
634
  });
615
- // When voice dictation is open, automatically speak incoming assistant replies
635
+ // WHILE THE MIC IS OPEN, A FINISHED REPLY IS READ ALOUD by the platform's
636
+ // voice, through the conversation's own mouth so speaking over it stops it.
637
+ // A room's reply is read part by part, each in its speaker's cast voice.
616
638
  const wasStreaming = useRef(false);
617
639
  useEffect(() => {
618
640
  if (wasStreaming.current && !chat.streaming && voice.open) {
619
641
  const msgs = chat.messages;
620
642
  const lastMsg = msgs[msgs.length - 1];
621
643
  if (lastMsg && lastMsg.role === "assistant" && typeof lastMsg.content === "string") {
622
- // A room's reply is read part by part, each in its speaker's voice.
623
644
  const parts = room.length > 1 ? speakers(lastMsg.content, room) : [{ who: withWhom, text: lastMsg.content }];
624
- const read = (i) => {
625
- if (i >= parts.length)
626
- return;
627
- speakAgent(parts[i].text, { agent: parts[i].who ?? withWhom ?? undefined, onEnd: () => read(i + 1) });
628
- };
629
- read(0);
645
+ void (async () => {
646
+ for (const part of parts) {
647
+ const said = cleanForSpeech(part.text);
648
+ if (said)
649
+ await voice.say(said, voiceOf(part.who ?? withWhom ?? undefined));
650
+ }
651
+ })();
630
652
  }
631
653
  }
632
654
  wasStreaming.current = chat.streaming;
633
- }, [chat.streaming, voice.open, chat.messages, withWhom, room]);
655
+ }, [chat.streaming, voice.open, voice.say, chat.messages, withWhom, room]);
634
656
  // A SEND THAT WAITS ONE RENDER. `send` closes over the turns it was rendered
635
657
  // with, so a send that first tidies the thread is held here and made from the
636
658
  // next render, where the tidied turns are the ones it sends.
@@ -771,6 +793,17 @@ function Thread({ placeholder = MESSAGE, initial, anonymous = false, heading, })
771
793
  }
772
794
  speak(text);
773
795
  }, [model, speak]);
796
+ /** The draft and the held files, sent as one turn. */
797
+ const submit = () => {
798
+ const text = draft.trim();
799
+ if (!text && !files.length)
800
+ return;
801
+ outgoing.current = images(files);
802
+ setDraft("");
803
+ setFiles([]);
804
+ setFileSaid(null);
805
+ guarded(compose(text, files));
806
+ };
774
807
  /*
775
808
  WHAT AN UNANSWERED QUESTION LOOKS LIKE: a turn of its own directly under it,
776
809
  naming who was asked, saying what went wrong in the reader's words, and
@@ -803,7 +836,7 @@ function Thread({ placeholder = MESSAGE, initial, anonymous = false, heading, })
803
836
  }, [said, chat.streaming, queued]);
804
837
  const failure = (question) => {
805
838
  const failed = failures[question.id];
806
- const text = String(question.content ?? "");
839
+ const text = words(question.content);
807
840
  const whom = addressed(text, room);
808
841
  const wrong = failed?.error ?? null;
809
842
  const how = wrong ? refusalOf(wrong) : null;
@@ -856,7 +889,31 @@ function Thread({ placeholder = MESSAGE, initial, anonymous = false, heading, })
856
889
  }, onCancel: () => {
857
890
  setAsking(null);
858
891
  } }));
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: () => {
892
+ return (_jsxs(YStack, { flex: 1, minH: 0, width: "100%", position: "relative", "data-slot": "chat-room",
893
+ // Entered and left once for every child crossed, so the veil follows a
894
+ // count rather than the last event.
895
+ onDragEnter: (e) => {
896
+ if (!Array.from(e.dataTransfer.types).includes("Files"))
897
+ return;
898
+ depth.current += 1;
899
+ setDragging(true);
900
+ }, onDragOver: (e) => {
901
+ if (!Array.from(e.dataTransfer.types).includes("Files"))
902
+ return;
903
+ e.preventDefault();
904
+ e.dataTransfer.dropEffect = "copy";
905
+ }, onDragLeave: () => {
906
+ depth.current = Math.max(0, depth.current - 1);
907
+ if (depth.current === 0)
908
+ setDragging(false);
909
+ }, onDrop: (e) => {
910
+ depth.current = 0;
911
+ setDragging(false);
912
+ if (!e.dataTransfer.files?.length)
913
+ return;
914
+ e.preventDefault();
915
+ void take(e.dataTransfer.files);
916
+ }, 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
917
  if (aside === null) {
861
918
  showAside("tasks");
862
919
  if (!pinned)
@@ -889,12 +946,15 @@ function Thread({ placeholder = MESSAGE, initial, anonymous = false, heading, })
889
946
  const question = shown.find((one) => one.id === String(turn.id).slice(FAILED.length + 1));
890
947
  return question ? failure(question) : null;
891
948
  }
949
+ // A QUESTION THAT CARRIED PICTURES draws them beside its words.
950
+ if (turn.role === "user" && Array.isArray(turn.content))
951
+ return _jsx(Parts, { parts: partsOf(turn.content), prose: (text) => _jsx(Prose, { text: text }) });
892
952
  const said = typeof turn.content === "string" ? turn.content : "";
893
953
  // WHO IS ANSWERING, while the answer has no words yet: the agents the
894
954
  // question addresses, or the whole room when it names nobody.
895
955
  if (turn.role === "assistant" && !said && chat.streaming && room.length) {
896
956
  const q = [...shown].reverse().find((one) => one.role === "user");
897
- const whom = addressed(String(q?.content ?? ""), room);
957
+ const whom = addressed(words(q?.content), room);
898
958
  return (_jsxs(Text, { fontSize: "$2", color: "$soft", children: [join(whom), " ", whom.length > 1 ? "are" : "is", " answering\u2026"] }));
899
959
  }
900
960
  // A ROOM'S ANSWER IS SEVERAL PEOPLE'S. Each opens a line with their
@@ -907,7 +967,7 @@ function Thread({ placeholder = MESSAGE, initial, anonymous = false, heading, })
907
967
  const at = shown.findIndex((one) => one.id === turn.id);
908
968
  const q = shown.slice(0, at).reverse().find((one) => one.role === "user");
909
969
  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()))
970
+ ? addressed(words(q?.content), room).filter((name) => !parts.some((part) => part.who?.toLowerCase() === name.toLowerCase()))
911
971
  : [];
912
972
  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
973
  }
@@ -926,13 +986,7 @@ function Thread({ placeholder = MESSAGE, initial, anonymous = false, heading, })
926
986
  column: { maxW: MEASURE }, composer: {
927
987
  value: draft,
928
988
  onChange: setDraft,
929
- onSend: () => {
930
- const text = draft.trim();
931
- if (!text)
932
- return;
933
- setDraft("");
934
- guarded(text);
935
- },
989
+ onSend: submit,
936
990
  // A CHARACTER IS WHO YOU ARE WRITING TO, so the field says so — and
937
991
  // `label` keeps the accessible name constant while it does, which is
938
992
  // the reason the component ships both: a placeholder that changes with
@@ -982,15 +1036,16 @@ function Thread({ placeholder = MESSAGE, initial, anonymous = false, heading, })
982
1036
  // package's button and `streaming` still has to become stop; it keeps
983
1037
  // `data-slot="composer-send"` so a driver finds it, and it is the same
984
1038
  // `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 })) })] })),
1039
+ // THE PAPERCLIP opens the file picker; the same files can be dropped on
1040
+ // the room.
1041
+ 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) => {
1042
+ void take(e.target.files);
1043
+ e.target.value = "";
1044
+ } })] })),
1045
+ // WHAT GOES WITH THE MESSAGE, above the frame: one chip a file, each
1046
+ // with its own remove, and the reason any file was refused.
1047
+ 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,
1048
+ 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
1049
  // WHAT THE PLAN HAS LEFT, under the frame and at its measure, for a
995
1050
  // plan holder only: Free keeps the free lane's own notice.
996
1051
  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);