@hanzo/rooms 0.1.64 → 0.1.66

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/Chat.js CHANGED
@@ -58,10 +58,10 @@ import { FileText, Image as ImageMark, Paperclip, X } from "lucide-react";
58
58
  import { FREE } from "./lib/ai.js";
59
59
  import { openThread, useOpen } from "./open.js";
60
60
  import { Beside, Framed } from "./Shell.js";
61
- import { SEED, Summary, look, useSites } from "./RightPane.js";
61
+ import { SEED, look, useSections, useSites } from "./RightPane.js";
62
62
  import { Share } from "./Share.js";
63
63
  import { ConversationMenu, ThreadMore, useThreadList } from "./thread.js";
64
- import { artifacts, lead, nameOf, PAGE, Panel, PanelToggle, prompt, renders, Reply, Served, useDeck, useKey, useMind, usePanel, frames, name as tabName, } from "@hanzo/build";
64
+ import { artifacts, lead, nameOf, PAGE, Panel, PanelToggle, prompt, renders, Reply, Served, Summary, useDeck, useKey, useMind, usePanel, frames, name as tabName, } from "@hanzo/build";
65
65
  import { checkoutUrl } from './lib/pay.js';
66
66
  import { Upgrade, useFree } from './Upgrade.js';
67
67
  import { enter, LOGIN } from './lib/destination.js';
@@ -1222,6 +1222,7 @@ function Thread({ placeholder = message(), initial, anonymous = false, heading,
1222
1222
  // Dev place goes from a host without it (hanzo.team).
1223
1223
  const pathed = useRooms().path !== null;
1224
1224
  const create = () => (pathed ? go(app("")) : window.location.assign(dev()));
1225
+ const sections = useSections({ at, said, held: files, deck, onCreate: create });
1225
1226
  /** The tabs' browser, as this conversation offers it: the org's sites to start from, and a page asked about, captured or opened in Dev. */
1226
1227
  const browsing = {
1227
1228
  suggested: sites,
@@ -1274,7 +1275,7 @@ function Thread({ placeholder = message(), initial, anonymous = false, heading,
1274
1275
  e.preventDefault();
1275
1276
  // A dropped folder arrives as entries; each file under it goes up with its path.
1276
1277
  void dropped(e.dataTransfer).then((list) => hold(at, list, via));
1277
- }, children: [dragging ? (_jsx(YStack, { "aria-hidden": true, position: "absolute", t: 0, r: 0, b: 0, l: 0, z: 10, items: "center", justify: "center", bg: "var(--surface-scrim)", borderWidth: 2, borderStyle: "dashed", borderColor: "$borderColor", rounded: "var(--pane-round)", pointerEvents: "none", children: _jsxs(XStack, { items: "center", gap: "$2", px: "$4", py: "$2.5", rounded: "$10", bg: "$background", borderWidth: 1, borderColor: "$borderColor", children: [_jsx(Paperclip, { size: 15, "aria-hidden": true }), _jsx(Text, { fontSize: "$4", color: "$ink", children: "Drop files to attach" })] }) })) : null, _jsxs(XStack, { "data-slot": "conversation-head", height: 52, items: "center", justify: "space-between", gap: "$3", pl: "$4", pr: "$2", shrink: 0, children: [_jsxs(XStack, { items: "center", gap: "$2", minW: 0, flex: 1, children: [mine ? (_jsx(Text, { fontSize: 15, lineHeight: 20, color: "$ink", numberOfLines: 1, shrink: 1, minW: 0, children: mine.title || "Untitled" })) : null, room.length ? (_jsx(XStack, { role: "list", "aria-label": "Agents in this chat", items: "center", gap: "$1.5", minW: 0, overflowX: "auto", "$platform-web": { scrollbarWidth: "none" }, children: room.map((name) => (_jsxs(XStack, { role: "listitem", items: "center", gap: "$1.5", pl: "$1", pr: "$0.5", py: "$0.5", rounded: 999, borderWidth: 1, borderColor: "$borderColor", shrink: 0, children: [_jsx(Face, { src: faces[name]?.avatar ?? (name === withWhom ? portrait : undefined), emoji: faces[name]?.emoji ?? (name === withWhom ? persona?.emoji : undefined), name: name, size: 22 }), _jsx(Text, { fontSize: "$2", fontWeight: "600", color: "$color", numberOfLines: 1, display: "none", $sm: { display: "flex" }, children: name }), _jsx(Box, { render: "button", onClick: () => openAgent(name), "aria-label": `Remove ${name} from this chat`, title: `Remove ${name}`, width: 24, height: 24, rounded: 999, items: "center", justify: "center", hoverStyle: { bg: "$hover" }, children: _jsx(X, { size: 13, "aria-hidden": true, color: "var(--soft, var(--muted-foreground))" }) })] }, name))) })) : null] }), _jsxs(XStack, { "data-slot": "conversation-controls", items: "center", gap: 2, shrink: 0, children: [open ? _jsx(Share, { thread: open }) : null, mine ? (_jsx(ConversationMenu, { threads: alone, active: open, children: _jsx(XStack, { children: _jsx(ThreadMore, { t: mine }) }) })) : null, _jsx(Summary, { at: at, said: said, held: files, deck: deck, onOpen: () => setPanel(true), onCreate: create }), _jsx(PanelToggle, { open: panel, onToggle: () => setPanel(!panel) })] })] }), wide && aside !== null ? (_jsx(Beside, { children: _jsx(Panel, { side: deck, kinds: NONE, onHide: () => setPanel(false), label: "Tabs", ...browsing }) })) : null, !wide && panel ? _jsx(Panel, { side: deck, kinds: NONE, sheet: true, onHide: () => setPanel(false), label: "Tabs", ...browsing }) : null, isFree(model) ? (_jsxs(Text, { fontSize: "$2", color: "$soft", text: "center", maxW: MEASURE, mx: "auto", px: "$4", py: "$2", children: [freeCopy.notice, " ", _jsx(Anchor, { href: site("/legal/privacy"), children: freeCopy.noticeCta })] })) : null, _jsx(ChatSurface, { ...chat, messages: shown, send: guarded,
1278
+ }, children: [dragging ? (_jsx(YStack, { "aria-hidden": true, position: "absolute", t: 0, r: 0, b: 0, l: 0, z: 10, items: "center", justify: "center", bg: "var(--surface-scrim)", borderWidth: 2, borderStyle: "dashed", borderColor: "$borderColor", rounded: "var(--pane-round)", pointerEvents: "none", children: _jsxs(XStack, { items: "center", gap: "$2", px: "$4", py: "$2.5", rounded: "$10", bg: "$background", borderWidth: 1, borderColor: "$borderColor", children: [_jsx(Paperclip, { size: 15, "aria-hidden": true }), _jsx(Text, { fontSize: "$4", color: "$ink", children: "Drop files to attach" })] }) })) : null, _jsxs(XStack, { "data-slot": "conversation-head", height: 52, items: "center", justify: "space-between", gap: "$3", pl: "$4", pr: "$2", shrink: 0, children: [_jsxs(XStack, { items: "center", gap: "$2", minW: 0, flex: 1, children: [mine ? (_jsx(Text, { fontSize: 15, lineHeight: 20, color: "$ink", numberOfLines: 1, shrink: 1, minW: 0, children: mine.title || "Untitled" })) : null, room.length ? (_jsx(XStack, { role: "list", "aria-label": "Agents in this chat", items: "center", gap: "$1.5", minW: 0, overflowX: "auto", "$platform-web": { scrollbarWidth: "none" }, children: room.map((name) => (_jsxs(XStack, { role: "listitem", items: "center", gap: "$1.5", pl: "$1", pr: "$0.5", py: "$0.5", rounded: 999, borderWidth: 1, borderColor: "$borderColor", shrink: 0, children: [_jsx(Face, { src: faces[name]?.avatar ?? (name === withWhom ? portrait : undefined), emoji: faces[name]?.emoji ?? (name === withWhom ? persona?.emoji : undefined), name: name, size: 22 }), _jsx(Text, { fontSize: "$2", fontWeight: "600", color: "$color", numberOfLines: 1, display: "none", $sm: { display: "flex" }, children: name }), _jsx(Box, { render: "button", onClick: () => openAgent(name), "aria-label": `Remove ${name} from this chat`, title: `Remove ${name}`, width: 24, height: 24, rounded: 999, items: "center", justify: "center", hoverStyle: { bg: "$hover" }, children: _jsx(X, { size: 13, "aria-hidden": true, color: "var(--soft, var(--muted-foreground))" }) })] }, name))) })) : null] }), _jsxs(XStack, { "data-slot": "conversation-controls", items: "center", gap: 2, shrink: 0, children: [open ? _jsx(Share, { thread: open }) : null, mine ? (_jsx(ConversationMenu, { threads: alone, active: open, children: _jsx(XStack, { children: _jsx(ThreadMore, { t: mine }) }) })) : null, _jsx(Summary, { sections: sections, onOpen: () => setPanel(true) }), _jsx(PanelToggle, { open: panel, onToggle: () => setPanel(!panel) })] })] }), wide && aside !== null ? (_jsx(Beside, { children: _jsx(Panel, { side: deck, kinds: NONE, onHide: () => setPanel(false), label: "Tabs", ...browsing }) })) : null, !wide && panel ? _jsx(Panel, { side: deck, kinds: NONE, sheet: true, onHide: () => setPanel(false), label: "Tabs", ...browsing }) : null, isFree(model) ? (_jsxs(Text, { fontSize: "$2", color: "$soft", text: "center", maxW: MEASURE, mx: "auto", px: "$4", py: "$2", children: [freeCopy.notice, " ", _jsx(Anchor, { href: site("/legal/privacy"), children: freeCopy.noticeCta })] })) : null, _jsx(ChatSurface, { ...chat, messages: shown, send: guarded,
1278
1279
  // AN EMPTY ROOM CENTRES ITS COMPOSER. While nothing has been said the
1279
1280
  // opening (the heading and the starters) and the composer sit as one
1280
1281
  // group in the middle of the height, with or without the plan bars in
package/dist/Rail.js CHANGED
@@ -1,9 +1,12 @@
1
1
  'use client';
2
2
  import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
3
  // The app's rail: its products as marks down the window's left edge, in one
4
- // order — Chat, Dev, Library, Scheduled tasks, Customize — then More, a menu of
5
- // the rest, and the account at the foot. It is how the app moves between Chat
6
- // and Dev; there is no switch anywhere else.
4
+ // order — Chat, Dev, Studio, Library, Scheduled tasks, Customize — then More, a
5
+ // menu of the rest, and the account at the foot. It is how the app moves
6
+ // between its products; there is no switch anywhere else.
7
+ //
8
+ // Studio is its own page, hanzo.ai/studio, opened in this tab: the app draws it
9
+ // there for a signed-in reader.
7
10
  //
8
11
  // Library, Scheduled tasks and Customize are Dev's own places (@hanzo/build's
9
12
  // Artifacts, Automations and Customize), raised to the rail; each is lit while
@@ -19,12 +22,13 @@ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-run
19
22
  // rail lies along the foot of the drawer (`flat`), the account at its end; the
20
23
  // drawer is too narrow for every mark, so Library, Scheduled tasks and
21
24
  // Customize lead More there.
22
- import { Boxes, Clock, Code2, Ellipsis, FolderGit2, LayoutTemplate, Library, ListTodo, MessageSquare, PanelLeft, Puzzle, Server, SquareTerminal, Users } from 'lucide-react';
25
+ import { Boxes, Clock, Code2, Ellipsis, FolderGit2, LayoutTemplate, Library, ListTodo, MessageSquare, PanelLeft, Puzzle, Server, SquareTerminal, Users, WandSparkles } from 'lucide-react';
23
26
  import { XStack, YStack } from '@hanzo/gui';
24
27
  import { route } from '@hanzo/build';
25
28
  import { DropdownMenu } from '@hanzo/ui';
26
29
  import { Me } from './Me.js';
27
- import { app, TABS, WORKSPACE } from './lib/host.js';
30
+ import { app, STUDIO, TABS, WORKSPACE } from './lib/host.js';
31
+ import { site } from './where.js';
28
32
  /** The rail's width, px. */
29
33
  export const RAIL = 48;
30
34
  /** A mark, px: a 36px square on a 44px pitch. */
@@ -91,5 +95,5 @@ export function Rail({ host, mode, at, open, toggle, flat = false, onChat, onDev
91
95
  { key: 'team', label: 'Team', icon: _jsx(Users, { size: 16, "aria-hidden": true }), onSelect: () => out(WORKSPACE) },
92
96
  ];
93
97
  const Stack = flat ? XStack : YStack;
94
- return (_jsxs(Stack, { "data-slot": "rail", render: "nav", "aria-label": "Products", ...(flat ? { pt: '$2', mt: '$2', borderTopWidth: 1, borderColor: '$borderColor', gap: '$1' } : { width: RAIL, flex: 1, pt: '$2', pb: '$2', gap: 8 }), shrink: 0, items: "center", children: [toggle ? (_jsx(YStack, { pb: 8, children: _jsx(Toggle, { open: open, onPress: toggle }) })) : null, _jsx(Mark, { label: "Chat", on: mode === 'chat', onPress: onChat, "data-slot": "rail-chat", children: _jsx(MessageSquare, { size: 18, "aria-hidden": true }) }), _jsx(Mark, { label: "Dev", on: mode === 'dev' && !here, onPress: onDev, "data-slot": "rail-dev", children: _jsx(Code2, { size: 18, "aria-hidden": true }) }), flat ? null : (_jsxs(_Fragment, { children: [_jsx(Mark, { label: "Library", on: here === 'library', onPress: to(RAISED.library), "data-slot": "rail-library", children: _jsx(Library, { size: 18, "aria-hidden": true }) }), _jsx(Mark, { label: "Scheduled tasks", on: here === 'scheduled', onPress: to(RAISED.scheduled), "data-slot": "rail-scheduled", children: _jsx(Clock, { size: 18, "aria-hidden": true }) }), _jsx(Mark, { label: "Customize", on: here === 'customize', onPress: to(RAISED.customize), "data-slot": "rail-customize", children: _jsx(Puzzle, { size: 18, "aria-hidden": true }) })] })), _jsx(DropdownMenu, { placement: flat ? 'top-start' : 'right-start', minWidth: 220, trigger: _jsx(XStack, { render: "button", "aria-label": "More", ...{ title: 'More' }, "data-slot": "rail-more", width: MARK, height: MARK, rounded: "$4", items: "center", justify: "center", cursor: "pointer", opacity: 0.62, hoverStyle: { bg: '$hover', opacity: 1 }, pressStyle: { bg: '$raised' }, focusVisibleStyle: { outlineWidth: 2, outlineStyle: 'solid', outlineColor: '$outlineColor' }, $touchable: { width: 44, height: 44 }, children: _jsx(Ellipsis, { size: 18, "aria-hidden": true }) }), items: rest }), _jsx(YStack, { flex: 1 }), _jsx(Me, { host: host, narrow: true, above: flat, onOrg: onOrg })] }));
98
+ return (_jsxs(Stack, { "data-slot": "rail", render: "nav", "aria-label": "Products", ...(flat ? { pt: '$2', mt: '$2', borderTopWidth: 1, borderColor: '$borderColor', gap: '$1' } : { width: RAIL, flex: 1, pt: '$2', pb: '$2', gap: 8 }), shrink: 0, items: "center", children: [toggle ? (_jsx(YStack, { pb: 8, children: _jsx(Toggle, { open: open, onPress: toggle }) })) : null, _jsx(Mark, { label: "Chat", on: mode === 'chat', onPress: onChat, "data-slot": "rail-chat", children: _jsx(MessageSquare, { size: 18, "aria-hidden": true }) }), _jsx(Mark, { label: "Dev", on: mode === 'dev' && !here, onPress: onDev, "data-slot": "rail-dev", children: _jsx(Code2, { size: 18, "aria-hidden": true }) }), _jsx(Mark, { label: "Studio", onPress: () => window.location.assign(site(STUDIO)), "data-slot": "rail-studio", children: _jsx(WandSparkles, { size: 18, "aria-hidden": true }) }), flat ? null : (_jsxs(_Fragment, { children: [_jsx(Mark, { label: "Library", on: here === 'library', onPress: to(RAISED.library), "data-slot": "rail-library", children: _jsx(Library, { size: 18, "aria-hidden": true }) }), _jsx(Mark, { label: "Scheduled tasks", on: here === 'scheduled', onPress: to(RAISED.scheduled), "data-slot": "rail-scheduled", children: _jsx(Clock, { size: 18, "aria-hidden": true }) }), _jsx(Mark, { label: "Customize", on: here === 'customize', onPress: to(RAISED.customize), "data-slot": "rail-customize", children: _jsx(Puzzle, { size: 18, "aria-hidden": true }) })] })), _jsx(DropdownMenu, { placement: flat ? 'top-start' : 'right-start', minWidth: 220, trigger: _jsx(XStack, { render: "button", "aria-label": "More", ...{ title: 'More' }, "data-slot": "rail-more", width: MARK, height: MARK, rounded: "$4", items: "center", justify: "center", cursor: "pointer", opacity: 0.62, hoverStyle: { bg: '$hover', opacity: 1 }, pressStyle: { bg: '$raised' }, focusVisibleStyle: { outlineWidth: 2, outlineStyle: 'solid', outlineColor: '$outlineColor' }, $touchable: { width: 44, height: 44 }, children: _jsx(Ellipsis, { size: 18, "aria-hidden": true }) }), items: rest }), _jsx(YStack, { flex: 1 }), _jsx(Me, { host: host, narrow: true, above: flat, onOrg: onOrg })] }));
95
99
  }
@@ -1,5 +1,5 @@
1
1
  import { type ChatMessage } from '@hanzo/ai/react';
2
- import { type Deck, type Kind, type Tabs } from '@hanzo/build';
2
+ import { type Deck, type Section, type Tabs } from '@hanzo/build';
3
3
  import { type Api } from './lib/files.js';
4
4
  import { type Held } from './pane.js';
5
5
  /** A conversation's panel before anyone has arranged it. */
@@ -31,28 +31,19 @@ export interface Mark {
31
31
  * the bytes this document still holds first (`localOf`).
32
32
  */
33
33
  export declare function look(deck: Deck, m: Mark, api: Api): Promise<void>;
34
- /** Chat's kinds of tab, over the conversation `said` and the files `held` for the next message. */
35
- export declare function useKinds({ at, said, held, deck }: {
36
- at: string;
37
- said: ChatMessage[];
38
- held: Held[];
39
- deck: Deck;
40
- }): Kind[];
41
34
  /**
42
- * The conversation's summary, from the outline mark in its header: what it
35
+ * The conversation's summary as sections (@hanzo/build `Summary`): what it
43
36
  * produced (Outputs, led by Create file or site, which starts a Dev run) and
44
37
  * what went into it (Sources, with the `+` that brings more). Each row opens in
45
- * a tab beside the conversation. A section with nothing in it keeps its heading
46
- * and one quiet line.
38
+ * a tab beside the conversation.
47
39
  */
48
- export declare function Summary({ at, said, held, deck, onOpen, onCreate }: {
40
+ export declare function useSections({ at, said, held, deck, onCreate }: {
49
41
  at: string;
50
42
  said: ChatMessage[];
51
43
  held: Held[];
52
44
  deck: Deck;
53
- onOpen: () => void;
54
45
  onCreate: () => void;
55
- }): import("react").JSX.Element;
46
+ }): Section[];
56
47
  /**
57
48
  * The organization's sites, as an empty tab suggests them: `GET /v1/projects`,
58
49
  * every row with a live address. Read once a conversation; nothing on a refusal.
package/dist/RightPane.js CHANGED
@@ -17,11 +17,10 @@ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-run
17
17
  // @hanzo/build (`useDeck`, scope `chat:<channel>`); the frame owns the column
18
18
  // it stands in (`Beside` in `Shell.tsx`).
19
19
  import { useEffect, useMemo, useState } from 'react';
20
- import { FileCode, Globe, Image as ImageMark, ListTodo, Paperclip, Plus, Search } from 'lucide-react';
21
- import { Box, DropdownMenu, DropdownMenuContent, DropdownMenuCheckboxItem, DropdownMenuItem, DropdownMenuLabel, DropdownMenuSeparator, DropdownMenuTrigger, Popover, PopoverContent, PopoverTrigger, Text, XStack, YStack, } from '@hanzo/ui';
20
+ import { FileCode, Globe, Image as ImageMark, Paperclip, Plus } from 'lucide-react';
21
+ import { Box, DropdownMenu, DropdownMenuContent, DropdownMenuCheckboxItem, DropdownMenuItem, DropdownMenuLabel, DropdownMenuSeparator, DropdownMenuTrigger, Text, } from '@hanzo/ui';
22
22
  import { useAi } from '@hanzo/ai/react';
23
23
  import { artifacts, name as titleOf, PAGE } from '@hanzo/build';
24
- import { Card, Quiet, Row } from './card.js';
25
24
  import { said as told } from './lib/attach.js';
26
25
  import { download, fileOf } from './lib/files.js';
27
26
  import { org, scope } from './lib/session.js';
@@ -116,32 +115,6 @@ export async function look(deck, m, api) {
116
115
  // A file forgotten or a store unreachable opens nothing; the row stays.
117
116
  }
118
117
  }
119
- /** A list of marks, every one of them; pressing one opens it. */
120
- function Marks({ kind, title, icon, marks, deck, action, children }) {
121
- const ai = useAi();
122
- return (_jsxs(Card, { kind: kind, icon: icon, title: title, action: action, children: [marks.length === 0 ? _jsx(Quiet, { children: children }) : null, marks.map((m) => {
123
- const Glyph = GLYPH[m.kind];
124
- return (_jsx(Row, { icon: _jsx(Glyph, { size: 16, "aria-hidden": true }), onPress: () => void look(deck, m, ai), children: m.name }, m.id));
125
- })] }));
126
- }
127
- /** Chat's kinds of tab, over the conversation `said` and the files `held` for the next message. */
128
- export function useKinds({ at, said, held, deck }) {
129
- const made = useMemo(() => outputs(said), [said]);
130
- const took = useMemo(() => sources(said, held), [said, held]);
131
- const done = useMemo(() => lookups(said), [said]);
132
- return [
133
- {
134
- id: 'artifacts',
135
- label: 'Artifacts',
136
- render: () => (_jsx(Marks, { kind: "outputs", title: "Outputs", icon: _jsx(FileCode, { size: 16, "aria-hidden": true }), marks: made, deck: deck, children: "This conversation has produced nothing yet." })),
137
- },
138
- {
139
- id: 'sources',
140
- label: 'Sources',
141
- render: () => (_jsxs(YStack, { gap: "$3", children: [_jsx(Marks, { kind: "sources", title: "Sources", icon: _jsx(Paperclip, { size: 16, "aria-hidden": true }), marks: took, deck: deck, action: _jsx(Add, { at: at }), children: "Nothing has been put into this conversation yet." }), _jsxs(Card, { kind: "lookups", icon: _jsx(Search, { size: 16, "aria-hidden": true }), title: "Looked up", children: [done.length === 0 ? _jsx(Quiet, { children: "Nothing was looked up for this conversation yet." }) : null, done.map((call) => (_jsxs(YStack, { gap: "$1", p: "$2", rounded: "$3", borderWidth: 1, borderColor: "$borderColor", children: [_jsx(Text, { fontSize: "$2", color: "$ink", children: call.name }), _jsx(Text, { fontSize: "$1", color: "$soft", numberOfLines: 2, children: call.asked }), _jsx(Text, { fontSize: "$1", color: "$soft", numberOfLines: 6, children: call.got ?? 'Running…' })] }, call.id)))] })] })),
142
- },
143
- ];
144
- }
145
118
  /**
146
119
  * A menu row's words.
147
120
  *
@@ -218,40 +191,31 @@ function Services() {
218
191
  .catch((e) => setStep(String(e?.message ?? 'Could not start')));
219
192
  }, children: _jsx(Word, { children: p.name }) }, p.id)))] }));
220
193
  }
221
- /** A row of the summary: its mark and its name, one press from a tab. */
222
- function Line({ icon, children, onPress, quiet = false }) {
223
- return (_jsxs(XStack, { render: onPress ? 'button' : undefined, role: onPress ? 'button' : undefined, onPress: onPress, items: "center", gap: "$2.5", px: "$2", minH: 30, width: "100%", rounded: "$3", cursor: onPress ? 'pointer' : undefined, hoverStyle: onPress ? { bg: '$hover' } : undefined, focusVisibleStyle: { outlineWidth: 2, outlineStyle: 'solid', outlineColor: '$outlineColor' }, children: [icon ? (_jsx(XStack, { width: 16, justify: "center", shrink: 0, opacity: 0.75, children: icon })) : null, _jsx(Text, { flex: 1, minW: 0, fontSize: "$2", color: quiet ? '$soft' : '$ink', numberOfLines: 1, children: children })] }));
224
- }
225
- /** A heading of the summary, with what it adds at its end. */
226
- const Head = ({ children, action }) => (_jsxs(XStack, { items: "center", justify: "space-between", px: "$2", pt: "$1.5", pb: "$1", minH: 28, children: [_jsx(Text, { fontSize: "$2", color: "$soft", children: children }), action] }));
227
194
  /**
228
- * The conversation's summary, from the outline mark in its header: what it
195
+ * The conversation's summary as sections (@hanzo/build `Summary`): what it
229
196
  * produced (Outputs, led by Create file or site, which starts a Dev run) and
230
197
  * what went into it (Sources, with the `+` that brings more). Each row opens in
231
- * a tab beside the conversation. A section with nothing in it keeps its heading
232
- * and one quiet line.
198
+ * a tab beside the conversation.
233
199
  */
234
- export function Summary({ at, said, held, deck, onOpen, onCreate }) {
200
+ export function useSections({ at, said, held, deck, onCreate }) {
235
201
  const ai = useAi();
236
- const [open, setOpen] = useState(false);
237
202
  const made = useMemo(() => outputs(said), [said]);
238
203
  const took = useMemo(() => sources(said, held), [said, held]);
239
204
  const done = useMemo(() => lookups(said), [said]);
240
- const show = (m) => {
241
- setOpen(false);
242
- void look(deck, m, ai);
243
- onOpen();
244
- };
245
- return (_jsxs(Popover, { open: open, onOpenChange: setOpen, allowFlip: true, children: [_jsx(PopoverTrigger, { children: _jsx(XStack, { render: "button", "data-slot": "summary-toggle", "aria-label": "Summary", "aria-haspopup": "dialog", "aria-expanded": open, ...{ title: 'Summary' }, width: 32, height: 32, rounded: "$3", items: "center", justify: "center", cursor: "pointer", bg: open ? '$hover' : 'transparent', hoverStyle: { bg: '$hover' }, focusVisibleStyle: { outlineWidth: 2, outlineStyle: 'solid', outlineColor: '$outlineColor' }, children: _jsx(ListTodo, { size: 16, "aria-hidden": true }) }) }), _jsx(PopoverContent, { solid: true, align: "end", p: 6, width: 300, children: _jsxs(YStack, { "data-slot": "summary", role: "dialog", "aria-label": "Summary", gap: 1, self: "stretch", width: "100%", maxH: "min(70dvh, 520px)", overflowY: "auto", overflowX: "hidden", children: [_jsx(Head, { children: "Outputs" }), _jsx(Line, { quiet: true, onPress: () => {
246
- setOpen(false);
247
- onCreate();
248
- }, children: "Create file or site" }), made.map((m) => {
249
- const Glyph = GLYPH[m.kind];
250
- return (_jsx(Line, { icon: _jsx(Glyph, { size: 15, "aria-hidden": true }), onPress: () => show(m), children: m.name }, m.id));
251
- }), _jsx(XStack, { height: 1, bg: "$borderColor", my: "$1.5", mx: "$1" }), _jsx(Head, { action: _jsx(Add, { at: at }), children: "Sources" }), done.map((call) => (_jsx(Line, { icon: _jsx(Globe, { size: 15, "aria-hidden": true }), onPress: () => show({ id: call.id, name: call.name, kind: 'file', body: [call.asked, call.got ?? ''].filter(Boolean).join('\n\n'), mime: 'text/plain' }), children: call.name === 'web_search' || call.name === 'search' ? 'Web search' : call.name }, call.id))), took.map((m) => {
252
- const Glyph = GLYPH[m.kind];
253
- return (_jsx(Line, { icon: _jsx(Glyph, { size: 15, "aria-hidden": true }), onPress: () => show(m), children: m.name }, m.id));
254
- }), !done.length && !took.length ? _jsx(Line, { quiet: true, children: "Nothing yet" }) : null] }) })] }));
205
+ return useMemo(() => {
206
+ const row = (m) => {
207
+ const Glyph = GLYPH[m.kind];
208
+ return { id: m.id, title: m.name, icon: _jsx(Glyph, { size: 15, "aria-hidden": true }), open: () => void look(deck, m, ai) };
209
+ };
210
+ const asked = done.map((call) => {
211
+ const m = { id: call.id, name: call.name, kind: 'file', body: [call.asked, call.got ?? ''].filter(Boolean).join('\n\n'), mime: 'text/plain' };
212
+ return { ...row(m), title: call.name === 'web_search' || call.name === 'search' ? 'Web search' : call.name, icon: _jsx(Globe, { size: 15, "aria-hidden": true }), detail: call.asked };
213
+ });
214
+ return [
215
+ { id: 'outputs', title: 'Outputs', lead: { title: 'Create file or site', run: onCreate }, entries: made.map(row) },
216
+ { id: 'sources', title: 'Sources', action: _jsx(Add, { at: at }), empty: 'Nothing yet', entries: [...asked, ...took.map(row)] },
217
+ ];
218
+ }, [at, made, took, done, deck, ai, onCreate]);
255
219
  }
256
220
  /**
257
221
  * The organization's sites, as an empty tab suggests them: `GET /v1/projects`,
@@ -267,7 +231,7 @@ export function useSites() {
267
231
  if (stop.signal.aborted)
268
232
  return;
269
233
  const all = list(Array.isArray(body) ? body : body?.data);
270
- setRows(all.filter((p) => typeof p.liveUrl === 'string' && /^https:\/\//.test(p.liveUrl)).map((p) => ({ title: p.name || p.slug || 'Site', url: p.liveUrl })));
234
+ setRows(all.filter((p) => typeof p.liveUrl === 'string' && p.liveUrl.startsWith('https://')).map((p) => ({ title: p.name || p.slug || 'Site', url: p.liveUrl })));
271
235
  })
272
236
  .catch(() => { });
273
237
  return () => stop.abort();
package/dist/host.js CHANGED
@@ -25,7 +25,7 @@ import { jsx as _jsx } from "react/jsx-runtime";
25
25
  import { createContext, useContext } from 'react';
26
26
  import { useIam, useOrganizations } from '@hanzo/iam/react';
27
27
  import { useAppearance } from '@hanzo/appearance';
28
- import { iam } from './lib/api.js';
28
+ import { api } from './lib/api.js';
29
29
  const Plain = ({ prefetch: _prefetch, ...rest }) => _jsx("a", { ...rest });
30
30
  const Context = createContext({
31
31
  router: {
@@ -59,7 +59,7 @@ export function useLook() {
59
59
  // passing through a room on the way to sign in sends nothing to the issuer.
60
60
  return useAppearance({
61
61
  org: org.id,
62
- account: accessToken ? { base: iam(), token: accessToken } : undefined,
62
+ account: accessToken ? { base: api(), token: accessToken } : undefined,
63
63
  });
64
64
  }
65
65
  /**
@@ -2,7 +2,7 @@
2
2
  import { jsx as _jsx } from "react/jsx-runtime";
3
3
  import { createContext, useContext, useState, useEffect, useMemo } from 'react';
4
4
  import { useIam, useIamIdentity, useIamToken, useOrganizations } from '@hanzo/iam/react';
5
- import { iam } from './api.js';
5
+ import { api } from './api.js';
6
6
  const AccountContext = createContext(undefined);
7
7
  function mapUser(u) {
8
8
  return {
@@ -54,8 +54,8 @@ function mapOrg(o, role) {
54
54
  // once the new address has proved it can receive one. That is the verification
55
55
  // flow's job, not a profile save's, so they are not here and this app does not
56
56
  // draw a control that pretends otherwise.
57
- /** IAM's self-scoped account door, at the host's IAM. */
58
- const account = () => `${iam()}/v1/iam/account`;
57
+ /** IAM's self-scoped account door, asked through the gateway (`api()`), which answers /v1/iam. */
58
+ const account = () => `${api()}/v1/iam/account`;
59
59
  // ── The read ───────────────────────────────────────────────────────────────
60
60
  //
61
61
  // THE SAME DOOR, and that is the point: the row this page edits is the row it
package/dist/lib/api.d.ts CHANGED
@@ -43,6 +43,11 @@ export declare function api(): string;
43
43
  * Two spellings of one setting are indistinguishable from a working one until
44
44
  * the day it is changed.
45
45
  */
46
+ /**
47
+ * The identity origin, for IAM's own PAGES (/account, /join). IAM's API is asked
48
+ * through the gateway, `api()`, which answers /v1/iam for every Hanzo origin, so
49
+ * a browser on hanzo.ai sends nothing to the identity origin.
50
+ */
46
51
  export declare function iam(): string;
47
52
  /**
48
53
  * The IAM application hanzo.ai signs people in through, and the one an invited
package/dist/lib/api.js CHANGED
@@ -64,6 +64,11 @@ export function api() {
64
64
  * Two spellings of one setting are indistinguishable from a working one until
65
65
  * the day it is changed.
66
66
  */
67
+ /**
68
+ * The identity origin, for IAM's own PAGES (/account, /join). IAM's API is asked
69
+ * through the gateway, `api()`, which answers /v1/iam for every Hanzo origin, so
70
+ * a browser on hanzo.ai sends nothing to the identity origin.
71
+ */
67
72
  export function iam() {
68
73
  if (onDevnet())
69
74
  return IAM_DEV;
@@ -46,6 +46,11 @@ export declare const TABS = "https://tabs.hanzo.ai";
46
46
  */
47
47
  export declare const CHAT = "/chat";
48
48
  export declare const DEV = "/dev";
49
+ /**
50
+ * Studio: making images, audio, video and documents, and editing them.
51
+ * hanzo.ai/studio, whose page draws the app for a signed-in reader.
52
+ */
53
+ export declare const STUDIO = "/studio";
49
54
  /** Chat on a conversation, or /chat itself for a new one. */
50
55
  export declare const talk: (id?: string | null) => string;
51
56
  /** Dev at a builder address (`route` in @hanzo/build); '' is New, /dev. What is not an address is New. */
package/dist/lib/host.js CHANGED
@@ -49,6 +49,11 @@ export const TABS = 'https://tabs.hanzo.ai';
49
49
  */
50
50
  export const CHAT = '/chat';
51
51
  export const DEV = '/dev';
52
+ /**
53
+ * Studio: making images, audio, video and documents, and editing them.
54
+ * hanzo.ai/studio, whose page draws the app for a signed-in reader.
55
+ */
56
+ export const STUDIO = '/studio';
52
57
  /** Chat on a conversation, or /chat itself for a new one. */
53
58
  export const talk = (id) => (id ? `${CHAT}/${encodeURIComponent(id)}` : CHAT);
54
59
  /** Dev at a builder address (`route` in @hanzo/build); '' is New, /dev. What is not an address is New. */
package/dist/look.js CHANGED
@@ -12,11 +12,11 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
12
12
  import { Text, YStack } from "@hanzo/ui";
13
13
  import { Appearance } from "@hanzo/appearance";
14
14
  import { useIam } from "@hanzo/iam/react";
15
- import { iam } from "./lib/api.js";
15
+ import { api } from "./lib/api.js";
16
16
  import { useOrg } from "./host.js";
17
17
  import { brand } from "./where.js";
18
18
  export function Look() {
19
19
  const { accessToken } = useIam();
20
20
  const org = useOrg();
21
- return (_jsxs(YStack, { gap: "$3", maxW: 420, children: [_jsx(Text, { fontSize: "$3", color: "$soft", children: "Kept with your account, so it follows you to every Hanzo surface." }), _jsx(Appearance, { org: org.id, orgName: org.name || brand(), account: { base: iam(), token: accessToken ?? undefined } })] }));
21
+ return (_jsxs(YStack, { gap: "$3", maxW: 420, children: [_jsx(Text, { fontSize: "$3", color: "$soft", children: "Kept with your account, so it follows you to every Hanzo surface." }), _jsx(Appearance, { org: org.id, orgName: org.name || brand(), account: { base: api(), token: accessToken ?? undefined } })] }));
22
22
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hanzo/rooms",
3
- "version": "0.1.64",
3
+ "version": "0.1.66",
4
4
  "description": "The workspace rooms — Home, Chat, Inbox, Contacts, Meet, Cal, Drive, Board, Work, Bots — and the frame that holds them, once, for every Hanzo surface.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -49,7 +49,7 @@
49
49
  "peerDependencies": {
50
50
  "@hanzo/ai": ">=0.6.23",
51
51
  "@hanzo/appearance": ">=0.2.6",
52
- "@hanzo/build": ">=0.2.53",
52
+ "@hanzo/build": ">=0.2.57",
53
53
  "@hanzo/composer": ">=0.2.0",
54
54
  "@hanzo/event": ">=0.3.56",
55
55
  "@hanzo/gui": "^8.3.5",
@@ -66,7 +66,7 @@
66
66
  "devDependencies": {
67
67
  "@hanzo/ai": "0.6.23",
68
68
  "@hanzo/appearance": "0.2.8",
69
- "@hanzo/build": "0.2.56",
69
+ "@hanzo/build": "0.2.57",
70
70
  "@hanzo/composer": "0.2.0",
71
71
  "@hanzo/design": "0.5.34",
72
72
  "@hanzo/event": "0.3.56",