@hanzo/rooms 0.1.89 → 0.1.91

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/App.js CHANGED
@@ -76,11 +76,13 @@ import { Palette } from './Palette.js';
76
76
  import { summons } from './seek.js';
77
77
  import { floor, pane } from './ground.js';
78
78
  import { pick } from './lib/session.js';
79
- import { app, go, place, studio, talk } from './lib/host.js';
79
+ import { app, go, place, studio, talk, TERMINALS } from './lib/host.js';
80
80
  import { Top } from './Top.js';
81
81
  import { FOOT, HEIGHT, TOP, useView } from './view.js';
82
82
  /** Studio, fetched the first time the reader stands in it. */
83
83
  const Studio = lazy(() => import('@hanzo/studio').then((m) => ({ default: m.Studio })));
84
+ /** Tabs, the same way: the terminals on the reader's machines (Terminals.tsx). */
85
+ const Terminals = lazy(() => import('./Terminals.js'));
84
86
  /** `/` is Chat on a new conversation. */
85
87
  const HOME = { mode: 'chat', thread: null };
86
88
  /** The bar's height, px: the reference window's title bar. */
@@ -98,7 +100,7 @@ const INSET = {
98
100
  left: 'max(var(--pane-gap), var(--safe-left))',
99
101
  };
100
102
  /** Where each mode was last, so the rail goes back to it. */
101
- const last = { chat: '', dev: '', studio: '' };
103
+ const last = { chat: '', dev: '', studio: '', tabs: '' };
102
104
  /** Whether a key press is the reader typing, where the app's keys are the field's. */
103
105
  const typing = (e) => {
104
106
  const el = e.target;
@@ -154,7 +156,13 @@ function Window({ bar }) {
154
156
  // A run floats two panes of its own, so the pane it stands in is not cut.
155
157
  const run = where !== null && route(where).kind === 'run';
156
158
  const asked = at.mode === 'chat' ? at.thread : null;
157
- const { thread } = useOpen();
159
+ const { thread, settings } = useOpen();
160
+ // A row of the account menu asks for Settings: the drawer goes away even when
161
+ // the address does not move (the reader was on that page already).
162
+ useEffect(() => {
163
+ if (settings)
164
+ setDrawer(false);
165
+ }, [settings]);
158
166
  const plans = where !== null && isPlans(where);
159
167
  useEffect(() => {
160
168
  if (plans)
@@ -239,10 +247,11 @@ function Window({ bar }) {
239
247
  const toChat = () => move(last.chat || talk());
240
248
  const toDev = () => move(last.dev || app(''));
241
249
  const toStudio = () => move(last.studio || studio(''));
250
+ const toTabs = () => move(TERMINALS);
242
251
  // Another organization: picked, and the app opened in it at the mode's New.
243
252
  const org = (o) => {
244
253
  pick(o);
245
- window.location.assign(mode === 'dev' ? app('') : mode === 'studio' ? studio('') : talk());
254
+ window.location.assign(mode === 'dev' ? app('') : mode === 'studio' ? studio('') : mode === 'tabs' ? TERMINALS : talk());
246
255
  };
247
256
  const sidebar = mode === 'studio' ? places(maker, (p) => maker.go(p)) : undefined;
248
257
  const flip = () => setClosed(!closed);
@@ -250,5 +259,5 @@ function Window({ bar }) {
250
259
  shut();
251
260
  setFinding(true);
252
261
  };
253
- return (_jsx(AiProvider, { client: client ?? undefined, children: _jsx(HostProvider, { host: host, children: _jsx(PrefsProvider, { children: _jsxs(YStack, { ref: view, "data-slot": "window", ...(bar ? floor : null), flex: 1, minW: 0, height: "100dvh", overflow: "hidden", "$max-md": { position: 'fixed', t: TOP, l: 0, r: 0, height: HEIGHT }, children: [bar ? (_jsxs(XStack, { "data-slot": "title-bar", height: BAR, shrink: 0, items: "center", gap: 2, pl: (bar.inset ?? 0) + 6, pr: "$2", ...bar.drag, children: [_jsx(XStack, { "$max-md": { display: 'none' }, children: _jsx(Toggle, { open: !closed, onPress: flip }) }), _jsx(XStack, { $md: { display: 'none' }, children: _jsx(Toggle, { open: drawer, onPress: () => setDrawer(true), label: "Open sidebar" }) }), _jsx(Mark, { label: "Back", "data-slot": "back", onPress: () => router.back(), children: _jsx(ArrowLeft, { size: 18, "aria-hidden": true }) }), _jsx(Mark, { label: "Forward", "data-slot": "forward", onPress: () => router.forward(), children: _jsx(ArrowRight, { size: 18, "aria-hidden": true }) }), _jsx(XStack, { flex: 1, self: "stretch", ...bar.drag })] })) : null, _jsxs(XStack, { "data-room": "", "data-app": mode, "data-slot": "frame", ...(bar ? null : floor), flex: 1, minW: 0, minH: 0, pr: "var(--pane-gap)", pb: "var(--pane-gap)", pt: bar ? 0 : 'var(--pane-gap)', "$max-md": { p: 0 }, children: [_jsx(YStack, { height: "100%", "$max-md": { display: 'none' }, children: _jsx(Rail, { host: host, mode: mode, at: where, open: !closed, toggle: bar ? undefined : flip, onChat: toChat, onDev: toDev, onStudio: toStudio, go: move, onOrg: org }) }), closed ? null : (_jsx(YStack, { "$max-md": { display: 'none' }, children: _jsx(Column, { host: host, mode: mode, thread: thread, go: move, onShut: () => setClosed(true), onSeek: seek, onBack: back, places: sidebar }) })), _jsxs(YStack, { "data-slot": "stage", flex: 1, minW: 0, minH: 0, ml: "var(--pane-gap)", "$max-md": { ml: 0, pb: FOOT }, children: [bar ? null : _jsx(Top, { host: host, mode: mode, places: sidebar, go: move, onMenu: () => setDrawer(true), onChat: toChat, onDev: toDev }), _jsx(XStack, { "data-slot": "panes", flex: 1, minW: 0, minH: 0, children: mode === 'chat' ? (_jsx(Paned, { framed: true, children: _jsx(Chat, {}) })) : mode === 'studio' ? (_jsx(YStack, { render: "main", "data-slot": "pane", ...pane(), overflow: "hidden", flex: 1, minW: 0, minH: 0, children: _jsx(YStack, { flex: 1, minH: 0, height: "100%", children: _jsx(Suspense, { fallback: null, children: _jsx(Studio, { host: maker, rail: false }) }) }) })) : (_jsx(YStack, { render: "main", "data-slot": "pane", ...(run ? null : { ...pane(), overflow: 'hidden' }), flex: 1, minW: 0, minH: 0, children: plans ? null : _jsx(Builder, { host: host, rail: false }) })) })] })] }), drawer ? (_jsxs(_Fragment, { children: [_jsx(YStack, { render: "button", "aria-label": "Close sidebar", onPress: () => setDrawer(false), bg: DIM, position: "absolute", t: 0, r: 0, b: 0, l: 0, z: "var(--z-overlay)", $md: { display: 'none' } }), _jsx(YStack, { "data-slot": "drawer", position: "absolute", t: INSET.top, b: INSET.bottom, l: INSET.left, width: "min(320px, calc(100% - 2 * var(--pane-gap) - 48px))", z: "var(--z-modal)", $md: { display: 'none' }, children: _jsx(Column, { host: host, mode: mode, thread: thread, go: move, onShut: shut, onSeek: seek, onBack: back, drawer: true, places: sidebar, foot: _jsx(Rail, { host: host, mode: mode, at: where, open: true, flat: true, onChat: toChat, onDev: toDev, onStudio: toStudio, go: move, onOrg: org }) }) })] })) : null, finding ? _jsx(Palette, { go: move, onClose: found, mode: mode, host: host }) : null] }) }) }) }));
262
+ return (_jsx(AiProvider, { client: client ?? undefined, children: _jsx(HostProvider, { host: host, children: _jsx(PrefsProvider, { children: _jsxs(YStack, { ref: view, "data-slot": "window", ...(bar ? floor : null), flex: 1, minW: 0, height: "100dvh", overflow: "hidden", "$max-md": { position: 'fixed', t: TOP, l: 0, r: 0, height: HEIGHT }, children: [bar ? (_jsxs(XStack, { "data-slot": "title-bar", height: BAR, shrink: 0, items: "center", gap: 2, pl: (bar.inset ?? 0) + 6, pr: "$2", ...bar.drag, children: [_jsx(XStack, { "$max-md": { display: 'none' }, children: _jsx(Toggle, { open: !closed, onPress: flip }) }), _jsx(XStack, { $md: { display: 'none' }, children: _jsx(Toggle, { open: drawer, onPress: () => setDrawer(true), label: "Open sidebar" }) }), _jsx(Mark, { label: "Back", "data-slot": "back", onPress: () => router.back(), children: _jsx(ArrowLeft, { size: 18, "aria-hidden": true }) }), _jsx(Mark, { label: "Forward", "data-slot": "forward", onPress: () => router.forward(), children: _jsx(ArrowRight, { size: 18, "aria-hidden": true }) }), _jsx(XStack, { flex: 1, self: "stretch", ...bar.drag })] })) : null, _jsxs(XStack, { "data-room": "", "data-app": mode, "data-slot": "frame", ...(bar ? null : floor), flex: 1, minW: 0, minH: 0, pr: "var(--pane-gap)", pb: "var(--pane-gap)", pt: bar ? 0 : 'var(--pane-gap)', "$max-md": { p: 0 }, children: [_jsx(YStack, { height: "100%", "$max-md": { display: 'none' }, children: _jsx(Rail, { host: host, mode: mode, at: where, open: !closed, toggle: bar ? undefined : flip, onChat: toChat, onDev: toDev, onStudio: toStudio, onTabs: toTabs, go: move, onOrg: org }) }), closed || mode === 'tabs' ? null : (_jsx(YStack, { "$max-md": { display: 'none' }, children: _jsx(Column, { host: host, mode: mode, thread: thread, go: move, onShut: () => setClosed(true), onSeek: seek, onBack: back, places: sidebar }) })), _jsxs(YStack, { "data-slot": "stage", flex: 1, minW: 0, minH: 0, ml: "var(--pane-gap)", "$max-md": { ml: 0, pb: FOOT }, children: [bar ? null : _jsx(Top, { host: host, mode: mode, places: sidebar, go: move, onMenu: () => setDrawer(true), onChat: toChat, onDev: toDev }), _jsx(XStack, { "data-slot": "panes", flex: 1, minW: 0, minH: 0, children: mode === 'chat' ? (_jsx(Paned, { framed: true, children: _jsx(Chat, {}) })) : mode === 'tabs' ? (_jsx(YStack, { render: "main", "data-slot": "pane", ...pane(), overflow: "hidden", flex: 1, minW: 0, minH: 0, children: _jsx(Suspense, { fallback: null, children: _jsx(Terminals, {}) }) })) : mode === 'studio' ? (_jsx(YStack, { render: "main", "data-slot": "pane", ...pane(), overflow: "hidden", flex: 1, minW: 0, minH: 0, children: _jsx(YStack, { flex: 1, minH: 0, height: "100%", children: _jsx(Suspense, { fallback: null, children: _jsx(Studio, { host: maker, rail: false }) }) }) })) : (_jsx(YStack, { render: "main", "data-slot": "pane", ...(run ? null : { ...pane(), overflow: 'hidden' }), flex: 1, minW: 0, minH: 0, children: plans ? null : _jsx(Builder, { host: host, rail: false }) })) })] })] }), drawer ? (_jsxs(_Fragment, { children: [_jsx(YStack, { render: "button", "aria-label": "Close sidebar", onPress: () => setDrawer(false), bg: DIM, position: "absolute", t: 0, r: 0, b: 0, l: 0, z: "var(--z-overlay)", $md: { display: 'none' } }), _jsx(YStack, { "data-slot": "drawer", position: "absolute", t: INSET.top, b: INSET.bottom, l: INSET.left, width: "min(320px, calc(100% - 2 * var(--pane-gap) - 48px))", z: "var(--z-modal)", $md: { display: 'none' }, children: _jsx(Column, { host: host, mode: mode, thread: thread, go: move, onShut: shut, onSeek: seek, onBack: back, drawer: true, places: sidebar, foot: _jsx(Rail, { host: host, mode: mode, at: where, open: true, flat: true, onChat: toChat, onDev: toDev, onStudio: toStudio, onTabs: toTabs, go: move, onOrg: org }) }) })] })) : null, finding ? _jsx(Palette, { go: move, onClose: found, mode: mode, host: host }) : null] }) }) }) }));
254
263
  }
package/dist/Column.js CHANGED
@@ -22,7 +22,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
22
22
  // its floor it shuts (`onShut`), as the sidebar's toggle shuts it.
23
23
  import { Text, XStack, YStack } from '@hanzo/gui';
24
24
  import { ArrowLeft, Bell, Search, Settings2, SquarePen } from 'lucide-react';
25
- import { DOTS, Gutter, NEW, own, path as written, sections, tabs, useSessions } from '@hanzo/build';
25
+ import { DOTS, Gutter, library, NEW, own, path as written, sections, tabs, useScheduled, useSessions } from '@hanzo/build';
26
26
  import { tidy } from './lib/title.js';
27
27
  import { DropdownMenu } from '@hanzo/ui';
28
28
  import { Sidebar, SidebarIconButton, SidebarItem, StatusDot } from '@hanzo/ui/chat';
@@ -98,7 +98,18 @@ export function Column({ host, mode, thread, go, onShut, onSeek, onBack, drawer
98
98
  const span = useSpan(SPAN, WIDTH, FLOOR, CEIL);
99
99
  const dev = (p) => host.go(p);
100
100
  const at = mode === 'dev' ? spot(host.path) : null;
101
- const groups = places ?? (at === 'settings' ? sections(host, dev) : at === 'customize' ? tabs(host, dev) : at ? [] : own(host, dev));
101
+ // Each place the rail holds lists its own rows: Library its kinds and Upload, Scheduled tasks its tasks and New task.
102
+ const scheduled = useScheduled(host, dev, at === 'scheduled');
103
+ const groups = places ??
104
+ (at === 'settings'
105
+ ? sections(host, dev)
106
+ : at === 'customize'
107
+ ? tabs(host, dev)
108
+ : at === 'library'
109
+ ? library(host, dev)
110
+ : at === 'scheduled'
111
+ ? scheduled
112
+ : own(host, dev));
102
113
  const open = (row) => (row.kind === 'chat' ? go(talk(row.id)) : dev(row.id));
103
114
  // Chat's New; Dev's is the first of its places.
104
115
  const create = () => {
@@ -107,5 +118,5 @@ export function Column({ host, mode, thread, go, onShut, onSeek, onBack, drawer
107
118
  };
108
119
  return (_jsxs(YStack, { "data-slot": "app-column", ref: drawer ? undefined : span.ref,
109
120
  // inline-style: `--span`, the width a drag rewrites on the element itself (span.ts); no prop sets a variable.
110
- style: drawer ? undefined : span.style, position: "relative", flex: drawer ? 1 : undefined, shrink: drawer ? 1 : 0, minW: 0, height: "100%", width: drawer ? undefined : 'var(--span)', children: [_jsxs(Sidebar, { "data-slot": "app-rail", role: "navigation", "aria-label": brand(), ...(drawer ? { ...pane(), ...FLOAT } : { bg: 'transparent', borderWidth: 0 }), ...FILL, px: "$2", pt: drawer ? '$2' : 0, pb: "$2", gap: "$0", children: [at ? (_jsxs(XStack, { "data-slot": "app-head", items: "center", gap: "$1", pl: 2, pr: 2, height: 52, shrink: 0, minW: 0, children: [_jsx(SidebarIconButton, { label: "Back", "data-slot": "place-back", onPress: onBack, ...HEAD, children: _jsx(ArrowLeft, { size: 16, "aria-hidden": true }) }), _jsx(Text, { flex: 1, minW: 0, fontSize: 18, lineHeight: 24, fontWeight: "600", color: "$ink", numberOfLines: 1, "data-slot": "place-name", children: TITLE[at] })] })) : (_jsxs(XStack, { "data-slot": "app-head", items: "center", gap: "$1", pl: "$2", pr: 2, height: 52, shrink: 0, minW: 0, children: [_jsx(Text, { flex: 1, minW: 0, fontSize: 18, lineHeight: 24, fontWeight: "600", color: "$ink", numberOfLines: 1, children: wordmark(mode) }), _jsx(Activity, { host: host }), _jsx(SidebarIconButton, { label: "Search", onPress: onSeek, "aria-haspopup": "dialog", ...HEAD, children: _jsx(Search, { size: 16, "aria-hidden": true }) })] })), mode === 'chat' ? (_jsx(YStack, { role: "list", gap: 2, shrink: 0, children: _jsx(YStack, { role: "listitem", children: _jsx(SidebarItem, { "data-slot": "app-new", ...ROW, active: !thread, bg: "transparent", hoverStyle: { bg: '$hover' }, icon: _jsx(SquarePen, { size: 16, "aria-hidden": true }), onPress: create, ...keys(create), children: _jsx(Text, { fontSize: "$3", color: "$ink", children: "New chat" }) }) }) })) : (_jsx(YStack, { "data-slot": "app-places", shrink: 0, children: groups.map((g, i) => (_jsxs(YStack, { pt: i ? '$2' : 0, children: [i && g.label ? (_jsx(Text, { id: `${uid}-${g.id}`, pl: "$2", pb: 2, fontSize: "$3", color: "$soft", children: g.label })) : null, _jsx(YStack, { role: "list", "aria-labelledby": i && g.label ? `${uid}-${g.id}` : undefined, "aria-label": i && g.label ? undefined : g.label || (at ? TITLE[at] : g.id), gap: 1, children: g.links.map((link) => (_jsx(YStack, { role: "listitem", children: _jsx(SidebarItem, { ...ROW, ...(link.id === NEW ? { 'data-slot': 'app-new' } : {}), active: link.active, icon: link.id === NEW ? _jsx(SquarePen, { size: 16, "aria-hidden": true }) : link.icon, onPress: link.onPress, ...keys(() => link.onPress?.()), children: _jsx(Text, { fontSize: "$3", color: link.active ? '$ink' : '$quiet', children: link.label }) }) }, link.id))) })] }, g.id))) })), mode === 'studio' || at ? null : _jsx(Recents, { host: host, mode: mode, thread: thread, onOpen: open, onNew: create }), at ? null : (_jsxs(YStack, { gap: "$2", pt: "$2", shrink: 0, children: [_jsx(Made, {}), _jsx(Setup, {})] })), foot] }), drawer ? null : (_jsx(Gutter, { side: "left", span: span.span, floor: FLOOR, ceil: CEIL, reset: WIDTH, onSpan: span.move, onKeep: span.keep, onShut: onShut, label: "Resize sidebar" }))] }));
121
+ style: drawer ? undefined : span.style, position: "relative", flex: drawer ? 1 : undefined, shrink: drawer ? 1 : 0, minW: 0, height: "100%", width: drawer ? undefined : 'var(--span)', children: [_jsxs(Sidebar, { "data-slot": "app-rail", role: "navigation", "aria-label": brand(), ...(drawer ? { ...pane(), ...FLOAT } : { bg: 'transparent', borderWidth: 0 }), ...FILL, px: "$2", pt: drawer ? '$2' : 0, pb: "$2", gap: "$0", children: [at ? (_jsxs(XStack, { "data-slot": "app-head", items: "center", gap: "$1", pl: 2, pr: 2, height: 52, shrink: 0, minW: 0, children: [_jsx(SidebarIconButton, { label: "Back", "data-slot": "place-back", onPress: onBack, ...HEAD, children: _jsx(ArrowLeft, { size: 16, "aria-hidden": true }) }), _jsx(Text, { flex: 1, minW: 0, fontSize: 18, lineHeight: 24, fontWeight: "600", color: "$ink", numberOfLines: 1, "data-slot": "place-name", children: TITLE[at] })] })) : (_jsxs(XStack, { "data-slot": "app-head", items: "center", gap: "$1", pl: "$2", pr: 2, height: 52, shrink: 0, minW: 0, children: [_jsx(Text, { flex: 1, minW: 0, fontSize: 18, lineHeight: 24, fontWeight: "600", color: "$ink", numberOfLines: 1, children: wordmark(mode) }), _jsx(Activity, { host: host }), _jsx(SidebarIconButton, { label: "Search", onPress: onSeek, "aria-haspopup": "dialog", ...HEAD, children: _jsx(Search, { size: 16, "aria-hidden": true }) })] })), mode === 'chat' ? (_jsx(YStack, { role: "list", gap: 2, shrink: 0, children: _jsx(YStack, { role: "listitem", children: _jsx(SidebarItem, { "data-slot": "app-new", ...ROW, active: !thread, bg: "transparent", hoverStyle: { bg: '$hover' }, icon: _jsx(SquarePen, { size: 16, "aria-hidden": true }), onPress: create, ...keys(create), children: _jsx(Text, { fontSize: "$3", color: "$ink", children: "New chat" }) }) }) })) : (_jsx(YStack, { "data-slot": "app-places", shrink: 0, children: groups.map((g, i) => (_jsxs(YStack, { pt: i ? '$2' : 0, children: [i && g.label ? (_jsx(Text, { id: `${uid}-${g.id}`, pl: "$2", pb: 2, fontSize: "$3", color: "$soft", children: g.label })) : null, _jsx(YStack, { role: "list", "aria-labelledby": i && g.label ? `${uid}-${g.id}` : undefined, "aria-label": i && g.label ? undefined : g.label || (at ? TITLE[at] : g.id), gap: 1, children: g.links.map((link) => (_jsx(YStack, { role: "listitem", children: _jsx(SidebarItem, { ...ROW, ...(link.id === NEW ? { 'data-slot': 'app-new' } : {}), active: link.active, icon: link.id === NEW ? _jsx(SquarePen, { size: 16, "aria-hidden": true }) : link.icon, onPress: link.onPress, ...keys(() => link.onPress?.()), children: _jsx(Text, { fontSize: "$3", color: link.active ? '$ink' : '$quiet', children: link.label }) }) }, link.id))) })] }, g.id))) })), mode === 'studio' || mode === 'tabs' || at ? null : _jsx(Recents, { host: host, mode: mode, thread: thread, onOpen: open, onNew: create }), at ? null : (_jsxs(YStack, { gap: "$2", pt: "$2", shrink: 0, children: [_jsx(Made, {}), _jsx(Setup, {})] })), foot] }), drawer ? null : (_jsx(Gutter, { side: "left", span: span.span, floor: FLOOR, ceil: CEIL, reset: WIDTH, onSpan: span.move, onKeep: span.keep, onShut: onShut, label: "Resize sidebar" }))] }));
111
122
  }
package/dist/Rail.d.ts CHANGED
@@ -31,7 +31,7 @@ export declare function Toggle({ open, onPress, label }: {
31
31
  onPress: () => void;
32
32
  label?: string;
33
33
  }): import("react").JSX.Element;
34
- export declare function Rail({ host, mode, at, open, toggle, flat, onChat, onDev, onStudio, go, onOrg, }: {
34
+ export declare function Rail({ host, mode, at, open, toggle, flat, onChat, onDev, onStudio, onTabs, go, onOrg, }: {
35
35
  host: Host;
36
36
  mode: Place['mode'];
37
37
  /** Dev's builder address, or null outside Dev. */
@@ -46,6 +46,7 @@ export declare function Rail({ host, mode, at, open, toggle, flat, onChat, onDev
46
46
  onChat: () => void;
47
47
  onDev: () => void;
48
48
  onStudio: () => void;
49
+ onTabs: () => void;
49
50
  /** Moves the app to an address of this page. */
50
51
  go: (address: string) => void;
51
52
  /** Work in another organization. */
package/dist/Rail.js CHANGED
@@ -1,8 +1,8 @@
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, Studio, Library, Scheduled tasks, Customize — then More, a
5
- // menu of the products on hosts of their own, and the account at the foot. It
4
+ // order — Chat, Dev, Studio, Tabs, Library, Scheduled tasks, Customize — then
5
+ // More (Team, on a host of its own, never in Studio), and the account at the foot. It
6
6
  // is how the app moves between its products; a phone, which has no rail,
7
7
  // switches Chat and Dev in its bar (Top.tsx).
8
8
  //
@@ -32,7 +32,7 @@ import { route } from '@hanzo/build';
32
32
  import { DropdownMenu } from '@hanzo/ui';
33
33
  import { out } from './help.js';
34
34
  import { Me } from './Me.js';
35
- import { app, TABS, WORKSPACE } from './lib/host.js';
35
+ import { app, WORKSPACE } from './lib/host.js';
36
36
  /** The rail's width, px. */
37
37
  export const RAIL = 48;
38
38
  /** A mark, px: a 36px square on a 44px pitch. */
@@ -64,22 +64,23 @@ export function Mark({ label, on = false, onPress, children, ...rest }) {
64
64
  export function Toggle({ open, onPress, label }) {
65
65
  return (_jsx(Mark, { label: label ?? (open ? 'Close sidebar' : 'Open sidebar'), "data-slot": "sidebar-toggle", "aria-expanded": open, onPress: onPress, children: _jsx(PanelLeft, { size: 18, "aria-hidden": true }) }));
66
66
  }
67
- export function Rail({ host, mode, at, open, toggle, flat = false, onChat, onDev, onStudio, go, onOrg, }) {
67
+ export function Rail({ host, mode, at, open, toggle, flat = false, onChat, onDev, onStudio, onTabs, go, onOrg, }) {
68
68
  const here = mode === 'dev' ? spot(at) : null;
69
69
  const to = (p) => () => go(app(p));
70
70
  // The products on hosts of their own, behind More; on a phone the raised places lead it.
71
+ // Studio offers no Team, so on a laptop it draws no More.
72
+ const team = mode !== 'studio';
71
73
  const rest = [
72
74
  ...(flat
73
75
  ? [
74
76
  { key: 'library', label: 'Library', icon: _jsx(Library, { size: 16, "aria-hidden": true }), onSelect: to(RAISED.library) },
75
77
  { key: 'scheduled', label: 'Scheduled tasks', icon: _jsx(Clock, { size: 16, "aria-hidden": true }), onSelect: to(RAISED.scheduled) },
76
78
  { key: 'customize', label: 'Customize', icon: _jsx(Puzzle, { size: 16, "aria-hidden": true }), onSelect: to(RAISED.customize) },
77
- { type: 'separator', key: 'raised' },
79
+ ...(team ? [{ type: 'separator', key: 'raised' }] : []),
78
80
  ]
79
81
  : []),
80
- { key: 'tabs', label: 'Tabs', icon: _jsx(SquareTerminal, { size: 16, "aria-hidden": true }), onSelect: () => out(TABS) },
81
- { key: 'team', label: 'Team', icon: _jsx(Users, { size: 16, "aria-hidden": true }), onSelect: () => out(WORKSPACE) },
82
+ ...(team ? [{ key: 'team', label: 'Team', icon: _jsx(Users, { size: 16, "aria-hidden": true }), onSelect: () => out(WORKSPACE) }] : []),
82
83
  ];
83
84
  const Stack = flat ? XStack : YStack;
84
- return (_jsxs(Stack, { "data-slot": "rail", render: "nav", "aria-label": "Products", ...(flat ? { pt: '$2', mt: '$2', borderTopWidth: 1, borderColor: '$borderColor', gap: '$1' } : { width: RAIL, flex: 1, pt: '$2', pb: '$2', gap: 8 }), shrink: 0, items: "center", children: [toggle ? (_jsx(YStack, { pb: 8, children: _jsx(Toggle, { open: open, onPress: toggle }) })) : null, _jsx(Mark, { label: "Chat", on: mode === 'chat', onPress: onChat, "data-slot": "rail-chat", children: _jsx(MessageSquare, { size: 18, "aria-hidden": true }) }), _jsx(Mark, { label: "Dev", on: mode === 'dev' && !here, onPress: onDev, "data-slot": "rail-dev", children: _jsx(Code2, { size: 18, "aria-hidden": true }) }), _jsx(Mark, { label: "Studio", on: mode === 'studio', onPress: onStudio, "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 })] }));
85
+ 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", on: mode === 'studio', onPress: onStudio, "data-slot": "rail-studio", children: _jsx(WandSparkles, { size: 18, "aria-hidden": true }) }), _jsx(Mark, { label: "Tabs", on: mode === 'tabs', onPress: onTabs, "data-slot": "rail-tabs", children: _jsx(SquareTerminal, { 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 }) })] })), rest.length ? (_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 })) : null, _jsx(YStack, { flex: 1 }), _jsx(Me, { host: host, narrow: true, above: flat, onOrg: onOrg })] }));
85
86
  }
@@ -0,0 +1 @@
1
+ export default function Terminals(): import("react").JSX.Element;
@@ -0,0 +1,15 @@
1
+ 'use client';
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ // Tabs in the app: the terminals on the reader's linked machines and sandboxes,
4
+ // tiled (@hanzo/tabs `Workspace`), wired by `useFleet` with the reader's own
5
+ // bearer against this host's cloud. The workspace holds no token; the fleet
6
+ // mints each pane's address. hanzo.ai/tabs draws it for a signed-in reader, the
7
+ // way /studio draws Studio.
8
+ import { Text, YStack } from '@hanzo/gui';
9
+ import { useFleet, Workspace } from '@hanzo/tabs';
10
+ import { api } from './lib/api.js';
11
+ import { bearer } from './lib/session.js';
12
+ export default function Terminals() {
13
+ const fleet = useFleet(bearer(), api());
14
+ return (_jsxs(YStack, { "data-slot": "terminals", flex: 1, minH: 0, minW: 0, children: [fleet.error ? (_jsx(Text, { role: "alert", px: "$4", py: "$2", fontSize: "$2", color: "$soft", children: `The machines could not be read: ${fleet.error}` })) : null, _jsx(Workspace, { hosts: fleet.hosts, mint: fleet.mint, onLaunch: fleet.launch })] }));
15
+ }
package/dist/Top.js CHANGED
@@ -54,5 +54,5 @@ export function Top({ host, mode, places, go, onMenu, onChat, onDev, }) {
54
54
  },
55
55
  }
56
56
  : { label: made?.label ?? 'New', onPress: made?.onPress };
57
- return (_jsxs(XStack, { "data-slot": "app-bar", height: `calc(${LINE}px + var(--safe-top))`, pt: "var(--safe-top)", px: "$4.5", items: "center", justify: "space-between", shrink: 0, $md: { display: 'none' }, children: [_jsx(Round, { label: "Open sidebar", "data-slot": "bar-menu", onPress: onMenu, children: _jsx(Lines, {}) }), _jsx(XStack, { "data-slot": "bar-mode", ...GLASS, height: LINE, p: "$1", items: "center", children: _jsxs(ToggleGroup, { type: "single", "aria-label": "App", value: mode === 'studio' ? '' : mode, disableDeactivation: true, onValueChange: (v) => (v === 'chat' ? onChat() : v === 'dev' ? onDev() : undefined), children: [_jsx(Side, { value: "chat", children: "Chat" }), _jsx(Side, { value: "dev", children: "Dev" })] }) }), _jsx(Round, { label: fresh.label, "data-slot": "bar-new", onPress: fresh.onPress, children: _jsx(SquarePen, { size: 20, "aria-hidden": true }) })] }));
57
+ return (_jsxs(XStack, { "data-slot": "app-bar", height: `calc(${LINE}px + var(--safe-top))`, pt: "var(--safe-top)", px: "$4.5", items: "center", justify: "space-between", shrink: 0, $md: { display: 'none' }, children: [_jsx(Round, { label: "Open sidebar", "data-slot": "bar-menu", onPress: onMenu, children: _jsx(Lines, {}) }), _jsx(XStack, { "data-slot": "bar-mode", ...GLASS, height: LINE, p: "$1", items: "center", children: _jsxs(ToggleGroup, { type: "single", "aria-label": "App", value: mode === 'chat' || mode === 'dev' ? mode : '', disableDeactivation: true, onValueChange: (v) => (v === 'chat' ? onChat() : v === 'dev' ? onDev() : undefined), children: [_jsx(Side, { value: "chat", children: "Chat" }), _jsx(Side, { value: "dev", children: "Dev" })] }) }), _jsx(Round, { label: fresh.label, "data-slot": "bar-new", onPress: fresh.onPress, children: _jsx(SquarePen, { size: 20, "aria-hidden": true }) })] }));
58
58
  }
@@ -30,7 +30,7 @@ export declare const ENTRY = "https://hanzo.team/?signin";
30
30
  * team's session has to live.
31
31
  */
32
32
  export declare const START = "https://hanzo.team/start";
33
- /** Hanzo Tabs, its own origin, opened from the column in a tab of its own. */
33
+ /** Hanzo Tabs on its own origin, which hanzo.team's shell opens; the app draws it in place at `TERMINALS`. */
34
34
  export declare const TABS = "https://tabs.hanzo.ai";
35
35
  /**
36
36
  * THE APP'S ADDRESSES ARE PATHS, in two modes at two roots. Chat is /chat, a
@@ -51,6 +51,11 @@ export declare const DEV = "/dev";
51
51
  * hanzo.ai/studio, whose page draws the app for a signed-in reader.
52
52
  */
53
53
  export declare const STUDIO = "/studio";
54
+ /**
55
+ * Tabs: the terminals on the reader's linked machines and sandboxes, tiled
56
+ * (@hanzo/tabs `Workspace`), drawn in the app at hanzo.ai/tabs.
57
+ */
58
+ export declare const TERMINALS = "/tabs";
54
59
  /** Studio at an address of its own (@hanzo/studio `route`): '' is New, /studio. */
55
60
  export declare const studio: (at: string) => string;
56
61
  /** Chat on a conversation, or /chat itself for a new one. */
@@ -67,6 +72,8 @@ export type Place = {
67
72
  } | {
68
73
  mode: 'studio';
69
74
  at: string;
75
+ } | {
76
+ mode: 'tabs';
70
77
  };
71
78
  /** The place a path names, or null when it names none of the app's. */
72
79
  export declare function place(pathname: string): Place | null;
package/dist/lib/host.js CHANGED
@@ -33,7 +33,7 @@ export const ENTRY = `${WORKSPACE}/?signin`;
33
33
  * team's session has to live.
34
34
  */
35
35
  export const START = `${WORKSPACE}/start`;
36
- /** Hanzo Tabs, its own origin, opened from the column in a tab of its own. */
36
+ /** Hanzo Tabs on its own origin, which hanzo.team's shell opens; the app draws it in place at `TERMINALS`. */
37
37
  export const TABS = 'https://tabs.hanzo.ai';
38
38
  /**
39
39
  * THE APP'S ADDRESSES ARE PATHS, in two modes at two roots. Chat is /chat, a
@@ -54,6 +54,11 @@ export const DEV = '/dev';
54
54
  * hanzo.ai/studio, whose page draws the app for a signed-in reader.
55
55
  */
56
56
  export const STUDIO = '/studio';
57
+ /**
58
+ * Tabs: the terminals on the reader's linked machines and sandboxes, tiled
59
+ * (@hanzo/tabs `Workspace`), drawn in the app at hanzo.ai/tabs.
60
+ */
61
+ export const TERMINALS = '/tabs';
57
62
  /** Studio at an address of its own (@hanzo/studio `route`): '' is New, /studio. */
58
63
  export const studio = (at) => (at ? `${STUDIO}/${at.replace(/^\/+/, '')}` : STUDIO);
59
64
  /** Chat on a conversation, or /chat itself for a new one. */
@@ -71,6 +76,8 @@ export function place(pathname) {
71
76
  return { mode: 'dev', at: path(parse(p.slice(DEV.length))) };
72
77
  if (p === STUDIO || p.startsWith(`${STUDIO}/`))
73
78
  return { mode: 'studio', at: p.slice(STUDIO.length + 1) };
79
+ if (p === TERMINALS)
80
+ return { mode: 'tabs' };
74
81
  if (!p.startsWith(`${CHAT}/`))
75
82
  return null;
76
83
  const id = p.slice(CHAT.length + 1);
@@ -90,6 +97,10 @@ export function page(pathname) {
90
97
  return pathname;
91
98
  if (at.mode === 'chat')
92
99
  return at.thread ? `${CHAT}/:id` : CHAT;
100
+ if (at.mode === 'tabs')
101
+ return TERMINALS;
102
+ if (at.mode === 'studio')
103
+ return STUDIO;
93
104
  const r = route(at.at);
94
105
  if (r.kind === 'run')
95
106
  return `${DEV}/run/:id`;
@@ -141,7 +152,7 @@ export function remember(mode) {
141
152
  export function resume() {
142
153
  try {
143
154
  const kept = window.localStorage.getItem(MODE);
144
- return kept === 'dev' ? DEV : kept === 'studio' ? STUDIO : CHAT;
155
+ return kept === 'dev' ? DEV : kept === 'studio' ? STUDIO : kept === 'tabs' ? TERMINALS : CHAT;
145
156
  }
146
157
  catch {
147
158
  return CHAT;
@@ -14,7 +14,7 @@
14
14
  * who was signed in, which reads as a permissions problem rather than a typo.
15
15
  * Two derivations of one name never agree; this is the one derivation.
16
16
  */
17
- import { superAdmin as sudoOf } from '@hanzo/iam/browser';
17
+ import { superAdmin as sudoOf } from '@hanzo/iam/identity';
18
18
  import { api } from './api.js';
19
19
  import { where } from '../where.js';
20
20
  /** The SDK's own keys, `hanzo_iam_`-prefixed. The far side of a boundary. */
package/dist/where.d.ts CHANGED
@@ -74,6 +74,6 @@ export declare const where: () => Addresses;
74
74
  /** The product's name, as the host states it. Read at call time, like every address. */
75
75
  export declare const brand: () => string;
76
76
  /** The product's name in each mode of the app: the host's brand in Chat (Hanzo AI, Hanzo Team), Hanzo Dev in Dev. */
77
- export declare const wordmark: (mode: 'chat' | 'dev' | 'studio') => string;
77
+ export declare const wordmark: (mode: 'chat' | 'dev' | 'studio' | 'tabs') => string;
78
78
  /** A page of the app's own, from wherever the rooms are mounted. */
79
79
  export declare const site: (path: string) => string;
package/dist/where.js CHANGED
@@ -12,6 +12,6 @@ export const where = () => addresses;
12
12
  /** The product's name, as the host states it. Read at call time, like every address. */
13
13
  export const brand = () => addresses.brand ?? 'Hanzo AI';
14
14
  /** The product's name in each mode of the app: the host's brand in Chat (Hanzo AI, Hanzo Team), Hanzo Dev in Dev. */
15
- export const wordmark = (mode) => (mode === 'dev' ? 'Hanzo Dev' : mode === 'studio' ? 'Hanzo Studio' : brand());
15
+ export const wordmark = (mode) => (mode === 'dev' ? 'Hanzo Dev' : mode === 'studio' ? 'Hanzo Studio' : mode === 'tabs' ? 'Hanzo Tabs' : brand());
16
16
  /** A page of the app's own, from wherever the rooms are mounted. */
17
17
  export const site = (path) => `${addresses.site}${path}`;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hanzo/rooms",
3
- "version": "0.1.89",
3
+ "version": "0.1.91",
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,13 +49,14 @@
49
49
  "peerDependencies": {
50
50
  "@hanzo/ai": ">=0.6.23",
51
51
  "@hanzo/appearance": ">=0.2.6",
52
- "@hanzo/build": ">=0.2.74",
52
+ "@hanzo/build": ">=0.2.77",
53
53
  "@hanzo/composer": ">=0.2.0",
54
54
  "@hanzo/event": ">=0.3.56",
55
55
  "@hanzo/gui": "^8.3.5",
56
- "@hanzo/iam": ">=0.21.33",
56
+ "@hanzo/iam": ">=0.21.35",
57
57
  "@hanzo/logo": ">=1.0.26",
58
58
  "@hanzo/studio": ">=0.1.0",
59
+ "@hanzo/tabs": ">=0.1.8",
59
60
  "@hanzo/ui": ">=8.29.0",
60
61
  "@hanzo/usage": ">=0.1.18",
61
62
  "@hanzo/voice": ">=0.1.16",
@@ -67,14 +68,15 @@
67
68
  "devDependencies": {
68
69
  "@hanzo/ai": "0.6.23",
69
70
  "@hanzo/appearance": "0.2.8",
70
- "@hanzo/build": "0.2.74",
71
+ "@hanzo/build": "0.2.77",
71
72
  "@hanzo/composer": "0.2.0",
72
73
  "@hanzo/design": "0.5.34",
73
74
  "@hanzo/event": "0.3.56",
74
75
  "@hanzo/gui": "8.3.5",
75
- "@hanzo/iam": "0.21.33",
76
+ "@hanzo/iam": "0.21.35",
76
77
  "@hanzo/logo": "1.0.26",
77
78
  "@hanzo/studio": "0.1.0",
79
+ "@hanzo/tabs": "github:hanzo-apps/tabs#af6f1e4c02a3ef7faa192a8310e193219fa5c1c5",
78
80
  "@hanzo/ui": "8.29.0",
79
81
  "@hanzo/usage": "0.1.18",
80
82
  "@hanzo/voice": "0.1.19",