@hanzo/rooms 0.1.0

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.
Files changed (235) hide show
  1. package/LICENSE-APACHE +205 -0
  2. package/LICENSE-MIT +21 -0
  3. package/README.md +23 -0
  4. package/dist/Account.d.ts +8 -0
  5. package/dist/Account.js +58 -0
  6. package/dist/Beluga.d.ts +12 -0
  7. package/dist/Beluga.js +152 -0
  8. package/dist/Billing.d.ts +1 -0
  9. package/dist/Billing.js +31 -0
  10. package/dist/Board.d.ts +9 -0
  11. package/dist/Board.js +447 -0
  12. package/dist/Boxes.d.ts +1 -0
  13. package/dist/Boxes.js +173 -0
  14. package/dist/CalScheduler.d.ts +15 -0
  15. package/dist/CalScheduler.js +196 -0
  16. package/dist/Chat.d.ts +35 -0
  17. package/dist/Chat.js +1049 -0
  18. package/dist/Column.d.ts +15 -0
  19. package/dist/Column.js +88 -0
  20. package/dist/Contacts.d.ts +1 -0
  21. package/dist/Contacts.js +301 -0
  22. package/dist/Directory.d.ts +24 -0
  23. package/dist/Directory.js +356 -0
  24. package/dist/Drive.d.ts +2 -0
  25. package/dist/Drive.js +466 -0
  26. package/dist/Find.d.ts +7 -0
  27. package/dist/Find.js +82 -0
  28. package/dist/Guide.d.ts +6 -0
  29. package/dist/Guide.js +272 -0
  30. package/dist/Home.d.ts +39 -0
  31. package/dist/Home.js +306 -0
  32. package/dist/Inbox.d.ts +9 -0
  33. package/dist/Inbox.js +193 -0
  34. package/dist/Market.d.ts +3 -0
  35. package/dist/Market.js +149 -0
  36. package/dist/Me.d.ts +27 -0
  37. package/dist/Me.js +251 -0
  38. package/dist/Meet.d.ts +9 -0
  39. package/dist/Meet.js +370 -0
  40. package/dist/ModeMark.d.ts +8 -0
  41. package/dist/ModeMark.js +18 -0
  42. package/dist/OrgSwitcher.d.ts +31 -0
  43. package/dist/OrgSwitcher.js +120 -0
  44. package/dist/Orgs.d.ts +10 -0
  45. package/dist/Orgs.js +582 -0
  46. package/dist/Profile.d.ts +4 -0
  47. package/dist/Profile.js +67 -0
  48. package/dist/Prose.d.ts +5 -0
  49. package/dist/Prose.js +111 -0
  50. package/dist/ProviderMark.d.ts +17 -0
  51. package/dist/ProviderMark.js +277 -0
  52. package/dist/Recents.d.ts +10 -0
  53. package/dist/Recents.js +91 -0
  54. package/dist/RightPane.d.ts +13 -0
  55. package/dist/RightPane.js +426 -0
  56. package/dist/Room.d.ts +8 -0
  57. package/dist/Room.js +50 -0
  58. package/dist/Scene.d.ts +12 -0
  59. package/dist/Scene.js +105 -0
  60. package/dist/Settings.d.ts +46 -0
  61. package/dist/Settings.js +321 -0
  62. package/dist/Setup.d.ts +1 -0
  63. package/dist/Setup.js +36 -0
  64. package/dist/Share.d.ts +3 -0
  65. package/dist/Share.js +130 -0
  66. package/dist/Shared.d.ts +1 -0
  67. package/dist/Shared.js +113 -0
  68. package/dist/Shell.d.ts +63 -0
  69. package/dist/Shell.js +1225 -0
  70. package/dist/Start.d.ts +1 -0
  71. package/dist/Start.js +96 -0
  72. package/dist/Support.d.ts +14 -0
  73. package/dist/Support.js +122 -0
  74. package/dist/Talk.d.ts +4 -0
  75. package/dist/Talk.js +107 -0
  76. package/dist/Upgrade.d.ts +11 -0
  77. package/dist/Upgrade.js +75 -0
  78. package/dist/Usage.d.ts +1 -0
  79. package/dist/Usage.js +21 -0
  80. package/dist/Vibe.d.ts +2 -0
  81. package/dist/Vibe.js +188 -0
  82. package/dist/Work.d.ts +12 -0
  83. package/dist/Work.js +256 -0
  84. package/dist/bots/Bots.d.ts +2 -0
  85. package/dist/bots/Bots.js +456 -0
  86. package/dist/bots/Lights.d.ts +8 -0
  87. package/dist/bots/Lights.js +13 -0
  88. package/dist/bots/api.d.ts +120 -0
  89. package/dist/bots/api.js +75 -0
  90. package/dist/bots/face.d.ts +5 -0
  91. package/dist/bots/face.js +13 -0
  92. package/dist/bots/ink.d.ts +7 -0
  93. package/dist/bots/ink.js +29 -0
  94. package/dist/bots/kit.d.ts +113 -0
  95. package/dist/bots/kit.js +43 -0
  96. package/dist/bots/runtime.d.ts +4 -0
  97. package/dist/bots/runtime.js +326 -0
  98. package/dist/card.d.ts +52 -0
  99. package/dist/card.js +52 -0
  100. package/dist/cast.d.ts +143 -0
  101. package/dist/cast.js +251 -0
  102. package/dist/connect.d.ts +29 -0
  103. package/dist/connect.js +64 -0
  104. package/dist/contacts-store.d.ts +111 -0
  105. package/dist/contacts-store.js +276 -0
  106. package/dist/conversations.d.ts +54 -0
  107. package/dist/conversations.js +98 -0
  108. package/dist/copy.d.ts +23 -0
  109. package/dist/copy.js +39 -0
  110. package/dist/crew.d.ts +36 -0
  111. package/dist/crew.js +304 -0
  112. package/dist/dev.d.ts +3 -0
  113. package/dist/dev.js +102 -0
  114. package/dist/enso.d.ts +70 -0
  115. package/dist/enso.js +112 -0
  116. package/dist/failure.d.ts +57 -0
  117. package/dist/failure.js +143 -0
  118. package/dist/ground.d.ts +32 -0
  119. package/dist/ground.js +32 -0
  120. package/dist/host.d.ts +76 -0
  121. package/dist/host.js +76 -0
  122. package/dist/index.d.ts +14 -0
  123. package/dist/index.js +12 -0
  124. package/dist/kept.d.ts +5 -0
  125. package/dist/kept.js +27 -0
  126. package/dist/lib/account.d.ts +49 -0
  127. package/dist/lib/account.js +229 -0
  128. package/dist/lib/ai.d.ts +46 -0
  129. package/dist/lib/ai.js +140 -0
  130. package/dist/lib/api.d.ts +43 -0
  131. package/dist/lib/api.js +62 -0
  132. package/dist/lib/bare.d.ts +9 -0
  133. package/dist/lib/bare.js +7 -0
  134. package/dist/lib/coding.d.ts +105 -0
  135. package/dist/lib/coding.js +87 -0
  136. package/dist/lib/destination.d.ts +102 -0
  137. package/dist/lib/destination.js +260 -0
  138. package/dist/lib/durable.d.ts +66 -0
  139. package/dist/lib/durable.js +107 -0
  140. package/dist/lib/first.d.ts +11 -0
  141. package/dist/lib/first.js +23 -0
  142. package/dist/lib/home.d.ts +61 -0
  143. package/dist/lib/home.js +54 -0
  144. package/dist/lib/host.d.ts +104 -0
  145. package/dist/lib/host.js +132 -0
  146. package/dist/lib/hostname.d.ts +1 -0
  147. package/dist/lib/hostname.js +14 -0
  148. package/dist/lib/hydrated.d.ts +1 -0
  149. package/dist/lib/hydrated.js +20 -0
  150. package/dist/lib/integrations.d.ts +21 -0
  151. package/dist/lib/integrations.js +1 -0
  152. package/dist/lib/intent.d.ts +6 -0
  153. package/dist/lib/intent.js +71 -0
  154. package/dist/lib/invite.d.ts +155 -0
  155. package/dist/lib/invite.js +245 -0
  156. package/dist/lib/limits.d.ts +55 -0
  157. package/dist/lib/limits.js +119 -0
  158. package/dist/lib/list.d.ts +9 -0
  159. package/dist/lib/list.js +11 -0
  160. package/dist/lib/mark.d.ts +13 -0
  161. package/dist/lib/mark.js +85 -0
  162. package/dist/lib/mix.d.ts +22 -0
  163. package/dist/lib/mix.js +22 -0
  164. package/dist/lib/models.d.ts +27 -0
  165. package/dist/lib/models.js +106 -0
  166. package/dist/lib/org.d.ts +109 -0
  167. package/dist/lib/org.js +140 -0
  168. package/dist/lib/path.d.ts +9 -0
  169. package/dist/lib/path.js +29 -0
  170. package/dist/lib/plans.d.ts +193 -0
  171. package/dist/lib/plans.js +310 -0
  172. package/dist/lib/prose.d.ts +13 -0
  173. package/dist/lib/prose.js +62 -0
  174. package/dist/lib/reach.d.ts +17 -0
  175. package/dist/lib/reach.js +70 -0
  176. package/dist/lib/rooms.json +23 -0
  177. package/dist/lib/served.d.ts +20 -0
  178. package/dist/lib/served.js +92 -0
  179. package/dist/lib/session.d.ts +211 -0
  180. package/dist/lib/session.js +493 -0
  181. package/dist/lib/share.d.ts +131 -0
  182. package/dist/lib/share.js +216 -0
  183. package/dist/lib/signout.d.ts +14 -0
  184. package/dist/lib/signout.js +40 -0
  185. package/dist/lib/tags.d.ts +27 -0
  186. package/dist/lib/tags.js +46 -0
  187. package/dist/lib/tap.d.ts +17 -0
  188. package/dist/lib/tap.js +9 -0
  189. package/dist/lib/tier.d.ts +115 -0
  190. package/dist/lib/tier.js +207 -0
  191. package/dist/lib/tiers.d.ts +9 -0
  192. package/dist/lib/tiers.js +141 -0
  193. package/dist/lib/todo.d.ts +125 -0
  194. package/dist/lib/todo.js +379 -0
  195. package/dist/look.d.ts +1 -0
  196. package/dist/look.js +21 -0
  197. package/dist/meters.d.ts +8 -0
  198. package/dist/meters.js +31 -0
  199. package/dist/model.d.ts +4 -0
  200. package/dist/model.js +44 -0
  201. package/dist/network.d.ts +64 -0
  202. package/dist/network.js +216 -0
  203. package/dist/open.d.ts +81 -0
  204. package/dist/open.js +149 -0
  205. package/dist/pane.d.ts +92 -0
  206. package/dist/pane.js +231 -0
  207. package/dist/progress.d.ts +46 -0
  208. package/dist/progress.js +51 -0
  209. package/dist/recent.d.ts +67 -0
  210. package/dist/recent.js +161 -0
  211. package/dist/rooms.d.ts +131 -0
  212. package/dist/rooms.js +195 -0
  213. package/dist/roster.d.ts +28 -0
  214. package/dist/roster.js +70 -0
  215. package/dist/span.d.ts +14 -0
  216. package/dist/span.js +50 -0
  217. package/dist/speech.d.ts +44 -0
  218. package/dist/speech.js +229 -0
  219. package/dist/stars.d.ts +16 -0
  220. package/dist/stars.js +75 -0
  221. package/dist/tabs.d.ts +26 -0
  222. package/dist/tabs.js +25 -0
  223. package/dist/team.d.ts +11 -0
  224. package/dist/team.gen.d.ts +10 -0
  225. package/dist/team.gen.js +11 -0
  226. package/dist/team.js +30 -0
  227. package/dist/tools.d.ts +35 -0
  228. package/dist/tools.js +143 -0
  229. package/dist/tours.d.ts +24 -0
  230. package/dist/tours.js +311 -0
  231. package/dist/waiting.d.ts +15 -0
  232. package/dist/waiting.js +17 -0
  233. package/dist/where.d.ts +54 -0
  234. package/dist/where.js +14 -0
  235. package/package.json +108 -0
@@ -0,0 +1,46 @@
1
+ export type Row = {
2
+ id: string;
3
+ label: string;
4
+ /**
5
+ * Where the row GOES, for one that leaves this list for the page that owns it.
6
+ *
7
+ * The row is still a button and not an anchor: the rail's contract is one verb
8
+ * per row, and the shell is the only thing that knows how this host moves. So
9
+ * this states the destination and `onPick` performs it.
10
+ */
11
+ href?: string;
12
+ /** One sentence, for a pane we have not built a screen for yet. */
13
+ note?: string;
14
+ };
15
+ /** The row Settings opens on when nothing has asked for another. */
16
+ export declare const FIRST: string;
17
+ /**
18
+ * The rail's second level: every settings row, grouped.
19
+ *
20
+ * It renders `SidebarItem` rather than a shape of its own so a settings row and
21
+ * a room row are the same row — the list under it changes, the column does not.
22
+ *
23
+ * `aria-current="true"` is stated rather than left to `active`, which publishes
24
+ * `page`. A settings row is the current item in a set, not the current page;
25
+ * the address never moves.
26
+ */
27
+ export declare function SettingsList({ active, filter, onPick, onBack, }: {
28
+ /** The open row's id. */
29
+ active: string;
30
+ /** The rail's own filter, narrowing these rows the way it narrows the rooms. */
31
+ filter: string;
32
+ /** Chosen. The shell decides what a row with an `href` does. */
33
+ onPick: (row: Row) => void;
34
+ /** Back to the rooms. */
35
+ onBack: () => void;
36
+ }): import("react").JSX.Element;
37
+ /**
38
+ * The room, while a settings row is open.
39
+ *
40
+ * It scrolls itself, the way the column beside a room does: `main` gives its
41
+ * child a floor and no overflow, so a pane that did not would push the whole
42
+ * shell down the page.
43
+ */
44
+ export declare function SettingsPane({ id }: {
45
+ id: string;
46
+ }): import("react").JSX.Element;
@@ -0,0 +1,321 @@
1
+ 'use client';
2
+ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ // Settings: the sidebar's second level, and the pane it opens in the room.
4
+ //
5
+ // TWO PARTS, ONE TAXONOMY. `SettingsList` is the rail's list — the shell swaps
6
+ // its own rows for these and back. `SettingsPane` is what fills the room while
7
+ // a row is chosen. Both read `GROUPS`, so the list can only offer a row the
8
+ // pane can draw.
9
+ //
10
+ // Which row is open is the shell's `showSettings` value and nothing here: a
11
+ // pane deep in the tree asks for Channels through the same store, and the rail
12
+ // and the room read that one answer.
13
+ //
14
+ // Every section that has a screen RENDERS IT HERE. Account, billing and usage
15
+ // are not links out to hanzo.id, billing.hanzo.ai and platform.hanzo.ai, because
16
+ // the surface is already packaged — @hanzo/usage carries the one usage panel
17
+ // every Hanzo product renders, @hanzo/iam carries the identity, and lib/plans
18
+ // carries the ladder. A row leaves this surface only when what it opens
19
+ // genuinely is not here: a legal document, another product's page, or a card
20
+ // form, which belongs to the payment host and nowhere else.
21
+ //
22
+ // The panes that are not yet built say so in one sentence rather than pretending
23
+ // to a screen. An honest empty pane is a smaller lie than an invented one.
24
+ import { dev } from './lib/host.js';
25
+ import { useMemo, useState } from 'react';
26
+ import { ArrowLeft, ArrowUpRight, ExternalLink } from 'lucide-react';
27
+ import { Box, Button, Separator, Text, XStack, YStack, } from '@hanzo/ui';
28
+ import { SidebarItem, SidebarSection } from '@hanzo/ui/chat';
29
+ import { tap } from './lib/tap.js';
30
+ import { useIam, useIamIdentity, useOrganizations } from '@hanzo/iam/react';
31
+ import { useSignOut } from './lib/signout.js';
32
+ import { useModels } from '@hanzo/ai/react';
33
+ import { ModelSelector, RESEARCH } from '@hanzo/ui/models';
34
+ import { UsagePanel } from '@hanzo/usage/panel';
35
+ import { Catalog } from './Directory.js';
36
+ import { useModel } from './model.js';
37
+ import { payPage, planName } from './lib/plans.js';
38
+ import { api } from './lib/api.js';
39
+ import { renewal, useSubscription, useTier } from './lib/tier.js';
40
+ import { sku, spendShown, useLimits } from './lib/limits.js';
41
+ import { Meters } from './meters.js';
42
+ import { Look } from './look.js';
43
+ import { ProviderMark } from './ProviderMark.js';
44
+ import { org, pick } from './lib/session.js';
45
+ import { site } from './where.js';
46
+ /** A choice among a few: a hairline edge and a corner. The fill and the edge's
47
+ * ink are the call site's, because they say which one is chosen. */
48
+ const CHOICE = { rounded: 8, borderWidth: 1, borderStyle: 'solid', cursor: 'pointer' };
49
+ const GROUPS = [
50
+ {
51
+ label: 'Settings',
52
+ rows: [
53
+ { id: 'general', label: 'General' },
54
+ { id: 'account', label: 'Account' },
55
+ { id: 'billing', label: 'Billing' },
56
+ { id: 'usage', label: 'Usage' },
57
+ { id: 'capabilities', label: 'Capabilities', note: 'What a model may reach for on your behalf.' },
58
+ { id: 'privacy', label: 'Privacy', href: site('/legal/privacy') },
59
+ { id: 'dev', label: 'Hanzo Dev', href: dev() },
60
+ { id: 'browser', label: 'Hanzo Browser', href: site('/extension') },
61
+ ],
62
+ },
63
+ {
64
+ label: 'Preferences',
65
+ rows: [
66
+ // DEFAULT MODEL, and the word matters beside Platform's Models: this row
67
+ // chooses which one answers, that one says which ones are served.
68
+ { id: 'model', label: 'Default model' },
69
+ {
70
+ id: 'agent',
71
+ label: 'Personal agent',
72
+ note: 'The agent that answers as you — its capabilities, its modes, and what it is allowed ' +
73
+ 'to do on your behalf. Nothing is bound to this surface yet, so there is nothing here ' +
74
+ 'to change; it is listed because this is where it will live.',
75
+ },
76
+ ],
77
+ },
78
+ {
79
+ label: 'Customize',
80
+ rows: [
81
+ { id: 'apps', label: 'Apps' },
82
+ { id: 'channels', label: 'Channels' },
83
+ { id: 'plugins', label: 'Plugins' },
84
+ { id: 'skills', label: 'Skills' },
85
+ { id: 'memory', label: 'Memory', note: 'Nothing is remembered between conversations yet.' },
86
+ ],
87
+ },
88
+ /**
89
+ * THE PLATFORM, from the workspace.
90
+ *
91
+ * Models draws here because `/v1/models` is a call this app already makes, so
92
+ * the pane can state what is actually served rather than describe it. Base,
93
+ * Functions and Realtime have no read this app makes, so each row goes to the
94
+ * page that already documents the product — a row that led to an invented
95
+ * screen would be worse than one that leaves.
96
+ */
97
+ {
98
+ label: 'Platform',
99
+ rows: [
100
+ { id: 'models', label: 'Models' },
101
+ { id: 'base', label: 'Base', href: site('/base') },
102
+ { id: 'functions', label: 'Functions', href: site('/functions') },
103
+ { id: 'realtime', label: 'Realtime', href: site('/realtime') },
104
+ { id: 'keys', label: 'API keys' },
105
+ ],
106
+ },
107
+ ];
108
+ /** Every row, flat — what the pane looks a chosen id up in. */
109
+ const ROWS = GROUPS.flatMap((g) => g.rows);
110
+ /** The row Settings opens on when nothing has asked for another. */
111
+ export const FIRST = ROWS[0].id;
112
+ /**
113
+ * The rail's second level: every settings row, grouped.
114
+ *
115
+ * It renders `SidebarItem` rather than a shape of its own so a settings row and
116
+ * a room row are the same row — the list under it changes, the column does not.
117
+ *
118
+ * `aria-current="true"` is stated rather than left to `active`, which publishes
119
+ * `page`. A settings row is the current item in a set, not the current page;
120
+ * the address never moves.
121
+ */
122
+ export function SettingsList({ active, filter, onPick, onBack, }) {
123
+ const q = filter.trim().toLowerCase();
124
+ const groups = q
125
+ ? GROUPS.map((g) => ({ ...g, rows: g.rows.filter((r) => r.label.toLowerCase().includes(q)) })).filter((g) => g.rows.length > 0)
126
+ : GROUPS;
127
+ return (_jsxs(_Fragment, { children: [_jsx(SidebarItem, { icon: _jsx(ArrowLeft, { size: 15, "aria-hidden": true }), onPress: onBack, children: "Workspace" }), q && groups.length === 0 ? (_jsxs(Text, { px: "$3", py: "$2", fontSize: "$2", color: "$quiet", children: ["No matches for \u201C", filter.trim(), "\u201D."] })) : null, groups.map((g) => (_jsx(SidebarSection, { label: g.label, children: g.rows.map((r) => (_jsx(SidebarItem, {
128
+ // The arrow says this row LEAVES. Without it a row that navigates
129
+ // away and one that draws a pane in place look identical until
130
+ // pressed.
131
+ icon: r.href ? _jsx(ArrowUpRight, { size: 13, "aria-hidden": true }) : undefined, active: r.id === active, "aria-current": r.id === active ? 'true' : undefined, onPress: () => onPick(r), children: r.label }, r.id))) }, g.label)))] }));
132
+ }
133
+ /**
134
+ * The room, while a settings row is open.
135
+ *
136
+ * It scrolls itself, the way the column beside a room does: `main` gives its
137
+ * child a floor and no overflow, so a pane that did not would push the whole
138
+ * shell down the page.
139
+ */
140
+ export function SettingsPane({ id }) {
141
+ const row = ROWS.find((r) => r.id === id);
142
+ return (_jsx(YStack, { flex: 1, minH: 0, overflow: "scroll", children: _jsxs(YStack, { p: "$5", gap: "$4", maxW: 860, children: [_jsx(Text, { fontSize: "$6", fontWeight: "600", color: "$ink", children: row?.label ?? 'Settings' }), _jsx(Pane, { id: id, note: row?.note })] }) }));
143
+ }
144
+ /** One pane. */
145
+ function Pane({ id, note }) {
146
+ if (id === 'apps' || id === 'channels' || id === 'plugins' || id === 'skills') {
147
+ return _jsx(Catalog, { tab: id });
148
+ }
149
+ if (id === 'general')
150
+ return _jsx(Look, {});
151
+ if (id === 'account')
152
+ return _jsx(Account, {});
153
+ if (id === 'billing')
154
+ return _jsx(Billing, {});
155
+ if (id === 'usage')
156
+ return _jsx(Usage, { meters: true });
157
+ if (id === 'keys')
158
+ return _jsx(Keys, {});
159
+ if (id === 'model')
160
+ return _jsx(Model, {});
161
+ if (id === 'models')
162
+ return _jsx(Served, {});
163
+ if (id === 'agent')
164
+ return _jsx(AgentSettings, {});
165
+ if (id === 'capabilities')
166
+ return _jsx(Capabilities, {});
167
+ if (id === 'memory')
168
+ return _jsx(MemorySettings, {});
169
+ return (_jsx(Text, { fontSize: "$3", color: "$soft", children: note ?? 'Nothing to configure here yet.' }));
170
+ }
171
+ /** One default in one place; agents may still bring their own model. */
172
+ function Model() {
173
+ const { models: served } = useModels();
174
+ // Hanzo models alone, as the composer's picker offers them (`sku`).
175
+ const models = useMemo(() => served.filter((m) => sku(m.id)), [served]);
176
+ const [model, choose] = useModel();
177
+ return (_jsxs(YStack, { gap: "$3", maxW: 420, children: [_jsx(Text, { fontSize: "$3", color: "$soft", children: "Used for new chats. An agent with its own model keeps that choice." }), _jsx(XStack, { self: "flex-start", children: _jsx(ModelSelector, { models: [...models, ...RESEARCH], value: model, onChange: choose, size: "sm" }) }), _jsx(YStack, { gap: "$2", mt: "$2", children: models
178
+ .filter((m) => !m.id.includes('openrouter'))
179
+ .slice(0, 8)
180
+ .map((m) => {
181
+ const isSel = model === m.id;
182
+ const p = m.id.startsWith('zen') ? 'zen' : 'enso';
183
+ return (_jsx(Box, { render: "button", onClick: () => choose(m.id), px: "$3", py: "$2", rounded: "$2", borderWidth: 1, borderColor: isSel ? '$ink' : '$borderColor', bg: isSel ? '$raised' : 'transparent', hoverStyle: { bg: '$hover' }, cursor: "pointer", children: _jsxs(XStack, { items: "center", gap: "$2.5", children: [_jsx(ProviderMark, { provider: p, size: 16 }), _jsx(Text, { fontSize: "$2", fontWeight: "600", color: "$ink", flex: 1, children: m.id }), isSel ? (_jsx(Text, { fontSize: "$1", color: "$soft", children: "Active" })) : null] }) }, m.id));
184
+ }) })] }));
185
+ }
186
+ /**
187
+ * WHAT THE GATEWAY SERVES, read from the same `/v1/models` the rooms read.
188
+ *
189
+ * Counted, never typed: the catalog moves without this file, so a number written
190
+ * here is wrong by the next deploy. An empty answer says the account reaches
191
+ * nothing rather than drawing a zero, and the mark comes from the model's own
192
+ * id so a family wears the logo of the lab that makes it.
193
+ */
194
+ function Served() {
195
+ const { models, loading, error } = useModels();
196
+ if (loading) {
197
+ return _jsx(Text, { fontSize: "$3", color: "$soft", children: "Reading the catalog." });
198
+ }
199
+ if (error) {
200
+ return _jsxs(Text, { fontSize: "$3", color: "$soft", children: ["The catalog did not answer: ", error.message] });
201
+ }
202
+ if (models.length === 0) {
203
+ return (_jsx(Text, { fontSize: "$3", color: "$soft", children: "No model answers on this account. Sign in to reach the ones your org opens." }));
204
+ }
205
+ return (_jsxs(YStack, { gap: "$3", children: [_jsxs(Text, { fontSize: "$3", color: "$soft", children: [models.length, " models answer at /v1/models on this account."] }), _jsx(YStack, { children: models.map((m) => (_jsxs(XStack, { items: "center", gap: "$2.5", py: "$2", borderBottomWidth: 1, borderColor: "$borderColor", children: [_jsx(ProviderMark, { model: m.id, org: m.owned_by, size: 16, ink: true }), _jsx(Text, { fontSize: "$2", color: "$ink", flex: 1, children: m.id }), m.owned_by ? _jsx(Text, { fontSize: "$1", color: "$soft", children: m.owned_by }) : null] }, m.id))) }), _jsx(XStack, { children: _jsx(Box, { render: _jsx("a", { href: site('/models') }), ...tap, children: _jsxs(XStack, { items: "center", gap: "$2", py: "$2", children: [_jsx(Text, { fontSize: "$3", color: "$ink", children: "Browse the model catalog" }), _jsx(ArrowUpRight, { size: 13, "aria-hidden": true })] }) }) })] }));
206
+ }
207
+ /** Who is signed in, and where they can act. */
208
+ function Account() {
209
+ const identity = useIamIdentity();
210
+ const { isAuthenticated } = useIam();
211
+ const logout = useSignOut();
212
+ const { organizations, currentOrgId, currentRole, switchOrg } = useOrganizations();
213
+ if (!isAuthenticated || !identity) {
214
+ return _jsx(Text, { fontSize: "$3", color: "$soft", children: "Sign in to see your account." });
215
+ }
216
+ return (_jsxs(YStack, { gap: "$4", children: [_jsxs(XStack, { items: "center", gap: "$3", children: [_jsx(YStack, { width: 44, height: 44, rounded: 22, bg: "$borderColor", items: "center", justify: "center", children: _jsx(Text, { fontSize: "$3", color: "$ink", children: identity.initials }) }), _jsxs(YStack, { gap: "$1", flex: 1, minW: 0, children: [_jsx(Text, { fontSize: "$4", color: "$ink", children: identity.name }), identity.email ? _jsx(Text, { fontSize: "$2", color: "$soft", children: identity.email }) : null] })] }), _jsx(Separator, {}), _jsxs(YStack, { gap: "$2", children: [_jsx(Text, { fontSize: "$2", color: "$soft", children: "Organizations" }), organizations.length === 0 ? (_jsx(Text, { fontSize: "$3", color: "$soft", children: "You are not a member of an organization." })) : (organizations.map((o) => {
217
+ const here = o.name === currentOrgId;
218
+ return (_jsx(Box, { render: _jsx("button", { type: "button" }), onClick: () => { pick(o.name); switchOrg(o.name); }, ...tap, children: _jsxs(XStack, { items: "center", justify: "space-between", py: "$2", width: "100%", gap: "$2", children: [_jsx(Text, { fontSize: "$3", color: here ? '$ink' : '$soft', children: o.displayName || o.name }), here && currentRole ? _jsx(Text, { fontSize: "$2", color: "$soft", children: currentRole }) : null] }) }, o.name));
219
+ }))] }), _jsx(Separator, {}), _jsx(XStack, { children: _jsx(Button, { size: "sm", onClick: () => logout(), children: "Sign out" }) })] }));
220
+ }
221
+ /** The plan, and the one control that has to leave. */
222
+ function Billing() {
223
+ const { isAuthenticated } = useIam();
224
+ const { tier } = useTier(isAuthenticated, org());
225
+ const sub = useSubscription(isAuthenticated, org());
226
+ // WHAT THE CUSTOMER BOUGHT, and its term: a plan is a subscription with usage
227
+ // limits, so its line is the price and the renewal, never a balance.
228
+ const plan = tier ? planName(tier.plan) || tier.tier.displayName || tier.tier.name : null;
229
+ return (_jsxs(YStack, { gap: "$4", children: [_jsx(Text, { fontSize: "$3", color: "$soft", children: "What this account is billed for, and what it has spent." }), plan ? (_jsx(Text, { fontSize: "$4", color: "$ink", children: sub ? `${plan} · ${renewal(sub)}` : plan })) : null, _jsx(Usage, { sections: { overview: true, chart: false, categories: true, breakdown: true, activity: false }, title: "Spend" }), _jsx(Separator, {}), _jsxs(YStack, { gap: "$2", children: [_jsx(Text, { fontSize: "$2", color: "$soft", children: "Payment" }), _jsx(Text, { fontSize: "$3", color: "$soft", children: "A card is entered on the payment host and nowhere else, so this is the one control here that opens another surface." }), _jsx(XStack, { children: _jsx(Box, { render: _jsx("a", { href: payPage(), target: "_blank", rel: "noreferrer" }), ...tap, children: _jsxs(XStack, { items: "center", gap: "$2", py: "$2", children: [_jsx(Text, { fontSize: "$3", color: "$ink", children: "Manage plan and payment method" }), _jsx(ExternalLink, { size: 13, "aria-hidden": true })] }) }) })] })] }));
230
+ }
231
+ /**
232
+ * The one usage panel every Hanzo product renders, reading as this account — for a
233
+ * reader with no plan. A plan holder's usage is the plan's shares (`meters` draws
234
+ * them where usage is the subject), never money (spendShown).
235
+ */
236
+ function Usage({ sections, title, meters = false }) {
237
+ const { accessToken, isAuthenticated } = useIam();
238
+ const read = useLimits(isAuthenticated, org());
239
+ if (!isAuthenticated || !accessToken) {
240
+ return _jsx(Text, { fontSize: "$3", color: "$soft", children: "Sign in to see your usage." });
241
+ }
242
+ if (read.limits?.plan)
243
+ return meters ? _jsx(Meters, { limits: read.limits, large: true }) : null;
244
+ if (!spendShown(read))
245
+ return null;
246
+ // The address is read HERE rather than at import: the panel sends the bearer
247
+ // with every read, and a credentialed read to an absolute address from a page
248
+ // the gateway does not admit dies in preflight — which drew this panel as an
249
+ // account that had spent nothing.
250
+ return _jsx(UsagePanel, { baseUrl: api(), token: accessToken, sections: sections, title: title, upgrade: payPage() });
251
+ }
252
+ /** Keys are minted and revoked through the console, which holds the one issuer. */
253
+ function Keys() {
254
+ return (_jsxs(YStack, { gap: "$2", children: [_jsx(Text, { fontSize: "$3", color: "$soft", children: "A key is shown once, at the moment it is minted. That happens where the issuer lives." }), _jsx(XStack, { children: _jsx(Box, { render: _jsx("a", { href: "https://platform.hanzo.ai/api-keys", target: "_blank", rel: "noreferrer" }), ...tap, children: _jsxs(XStack, { items: "center", gap: "$2", py: "$2", children: [_jsx(Text, { fontSize: "$3", color: "$ink", children: "Manage API keys" }), _jsx(ExternalLink, { size: 13, "aria-hidden": true })] }) }) })] }));
255
+ }
256
+ const HISTORICAL_PERSONAS = [
257
+ { id: 'architect', name: 'Software Architect', role: 'Full-stack & systems design' },
258
+ { id: 'satoshi', name: 'Satoshi Nakamoto', role: 'Distributed cryptography & consensus' },
259
+ { id: 'turing', name: 'Alan Turing', role: 'Algorithmic logic & formal verification' },
260
+ { id: 'ada', name: 'Ada Lovelace', role: 'Computational precision & analysis' },
261
+ { id: 'davinci', name: 'Leonardo da Vinci', role: 'Polymathic engineering & design' },
262
+ ];
263
+ function AgentSettings() {
264
+ const [instructions, setInstructions] = useState(() => {
265
+ if (typeof window === 'undefined')
266
+ return '';
267
+ return localStorage.getItem('hanzo.agent.instructions') || 'You are an autonomous AI software engineer at Hanzo. Think step-by-step and write robust code.';
268
+ });
269
+ const [persona, setPersona] = useState(() => {
270
+ if (typeof window === 'undefined')
271
+ return 'architect';
272
+ return localStorage.getItem('hanzo.agent.persona') || 'architect';
273
+ });
274
+ const [autonomy, setAutonomy] = useState(() => {
275
+ if (typeof window === 'undefined')
276
+ return 'autonomous';
277
+ return localStorage.getItem('hanzo.agent.autonomy') || 'autonomous';
278
+ });
279
+ const [saved, setSaved] = useState(false);
280
+ const save = () => {
281
+ localStorage.setItem('hanzo.agent.instructions', instructions);
282
+ localStorage.setItem('hanzo.agent.persona', persona);
283
+ localStorage.setItem('hanzo.agent.autonomy', autonomy);
284
+ setSaved(true);
285
+ setTimeout(() => setSaved(false), 2000);
286
+ };
287
+ return (_jsxs(YStack, { gap: "$4", maxW: 480, children: [_jsx(Text, { fontSize: "$3", color: "$soft", children: "Nothing is bound to this surface yet \u2014 when you bind a personal agent, its persona, tools and memory live here rather than in a shared team." }), _jsxs(YStack, { gap: "$1.5", children: [_jsx(Text, { fontSize: "$3", fontWeight: "600", color: "$ink", children: "Persona & Historical Character" }), _jsx(Text, { fontSize: "$2", color: "$soft", children: "Pick an intellectual archetype for agent reasoning and tone." }), _jsx(XStack, { flexWrap: "wrap", gap: "$2", pt: "$1", children: HISTORICAL_PERSONAS.map((p) => {
288
+ const active = persona === p.id;
289
+ return (_jsx(Box, { render: _jsx("button", { type: "button" }), ...tap, onClick: () => setPersona(p.id), ...CHOICE, bg: active ? 'var(--edge, var(--white-14))' : 'transparent', borderColor: active ? 'var(--borderColor, var(--white-22))' : 'var(--white-08)', py: 6, px: 12, children: _jsx(Text, { fontSize: "$2", fontWeight: active ? '600' : '400', color: active ? '$ink' : '$soft', children: p.name }) }, p.id));
290
+ }) })] }), _jsx(Separator, {}), _jsxs(YStack, { gap: "$1.5", children: [_jsx(Text, { fontSize: "$3", fontWeight: "600", color: "$ink", children: "System Prompt Instructions" }), _jsx(Text, { fontSize: "$2", color: "$soft", children: "Custom system directions prepended to all coding and research runs." }), _jsx(Text, { render: _jsx("textarea", { value: instructions, onChange: (e) => setInstructions(e.target.value), rows: 4 }), width: "100%", bg: "var(--panel, color-mix(in srgb, var(--pure-black) 30%, transparent))", color: "inherit", borderWidth: 1, borderColor: "var(--borderColor, var(--white-15))", rounded: 8, p: 10, fontSize: "$2", fontFamily: "$mono" })] }), _jsx(Separator, {}), _jsxs(YStack, { gap: "$1.5", children: [_jsx(Text, { fontSize: "$3", fontWeight: "600", color: "$ink", children: "Autonomy & Permissions" }), _jsxs(XStack, { gap: "$2", children: [_jsxs(Box, { render: _jsx("button", { type: "button" }), ...tap, onClick: () => setAutonomy('autonomous'), ...CHOICE, flex: 1, p: 10, bg: autonomy === 'autonomous' ? 'var(--edge, var(--white-14))' : 'transparent', borderColor: autonomy === 'autonomous' ? 'var(--borderColor, var(--white-22))' : 'var(--white-08)', children: [_jsx(Text, { fontSize: "$2", fontWeight: "600", color: "$ink", children: "Autonomous" }), _jsx(Text, { fontSize: "$1", color: "$soft", children: "Executes tools & writes code without interrupting." })] }), _jsxs(Box, { render: _jsx("button", { type: "button" }), ...tap, onClick: () => setAutonomy('supervised'), ...CHOICE, flex: 1, p: 10, bg: autonomy === 'supervised' ? 'var(--edge, var(--white-14))' : 'transparent', borderColor: autonomy === 'supervised' ? 'var(--borderColor, var(--white-22))' : 'var(--white-08)', children: [_jsx(Text, { fontSize: "$2", fontWeight: "600", color: "$ink", children: "Supervised" }), _jsx(Text, { fontSize: "$1", color: "$soft", children: "Requests confirmation before file writes and commands." })] })] })] }), _jsx(XStack, { self: "flex-start", pt: "$2", children: _jsx(Button, { size: "sm", onClick: save, children: saved ? 'Saved' : 'Save agent preferences' }) })] }));
291
+ }
292
+ /**
293
+ * WHAT A RUN MAY REACH FOR — the four capabilities, described and not measured.
294
+ *
295
+ * Each row used to carry a badge reading "Enabled", and the MCP row read
296
+ * "Active (:8080/mcp)". Nothing asked. No call stands behind any of the four,
297
+ * so the badges said "on" for a reader whose run had none of it, and the port
298
+ * was one developer's machine printed on everybody's settings pane. A green
299
+ * light nobody wired is worse than no light: it is read as a check that passed.
300
+ *
301
+ * These are facts about the PRODUCT — what a run is allowed to do — which is
302
+ * what this pane is for. Whether a given run got them is a fact about that run,
303
+ * and the run is where it belongs.
304
+ */
305
+ function Capabilities() {
306
+ return (_jsxs(YStack, { gap: "$3", maxW: 480, children: [_jsx(Text, { fontSize: "$3", color: "$soft", children: "What models and agent swarms are granted access to during tasks." }), _jsx(YStack, { gap: "$2", pt: "$1", children: [
307
+ { title: 'Filesystem (Read / Write)', desc: 'Create, modify, and delete workspace files in the target repository.' },
308
+ { title: 'Shell & Terminal Exec', desc: 'Execute build, test, and shell commands in isolated sandbox VMs.' },
309
+ { title: 'Live Web & Search', desc: 'Query internet search APIs and fetch web documentation.' },
310
+ { title: 'MCP Protocol Server', desc: 'Expose local and remote tools via Model Context Protocol at /mcp.' },
311
+ ].map((cap) => (_jsxs(YStack, { gap: "$0.5", p: "$2.5", rounded: "$3", borderWidth: 1, borderColor: "$borderColor", children: [_jsx(Text, { fontSize: "$3", fontWeight: "600", color: "$ink", children: cap.title }), _jsx(Text, { fontSize: "$2", color: "$soft", children: cap.desc })] }, cap.title))) })] }));
312
+ }
313
+ function MemorySettings() {
314
+ const [cleared, setCleared] = useState(false);
315
+ const clearMemory = () => {
316
+ localStorage.removeItem('hanzo.chat.history');
317
+ setCleared(true);
318
+ setTimeout(() => setCleared(false), 2000);
319
+ };
320
+ return (_jsxs(YStack, { gap: "$3", maxW: 480, children: [_jsx(Text, { fontSize: "$3", color: "$soft", children: "Semantic memory and conversation recall across sessions." }), _jsxs(YStack, { gap: "$0.5", p: "$2.5", rounded: "$3", borderWidth: 1, borderColor: "$borderColor", children: [_jsx(Text, { fontSize: "$3", fontWeight: "600", color: "$ink", children: "Semantic Vector Index" }), _jsx(Text, { fontSize: "$2", color: "$soft", children: "Automatically indexes past workspace sessions into vector memory." })] }), _jsx(XStack, { self: "flex-start", pt: "$2", children: _jsx(Button, { size: "sm", variant: "outline", onClick: clearMemory, children: cleared ? 'Memory cleared' : 'Clear local memory cache' }) })] }));
321
+ }
@@ -0,0 +1 @@
1
+ export declare function Setup(): import("react").JSX.Element;
package/dist/Setup.js ADDED
@@ -0,0 +1,36 @@
1
+ 'use client';
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ // The first-run steps, offered: a card in the app's column that opens
4
+ // /onboarding and can be put away. Never a redirect — chat is free and nothing
5
+ // stands in front of it.
6
+ //
7
+ // Offered only to an account that has not begun: no organization in its token
8
+ // and a step still owed by IAM's record (@hanzo/ui/onboarding `firstRun`). An
9
+ // account with an organization has history, and is onboarded whatever the
10
+ // record says.
11
+ import { useEffect, useState } from 'react';
12
+ import { SizableText, Text, XStack, YStack } from '@hanzo/gui';
13
+ import { Sparkles, X } from 'lucide-react';
14
+ import { firstRun } from '@hanzo/ui/onboarding';
15
+ import { useKept } from './kept.js';
16
+ import { orgs } from './lib/session.js';
17
+ import { api } from './lib/api.js';
18
+ import { site } from './where.js';
19
+ /** Put away in this browser. */
20
+ const LATER = 'hanzo.onboarding.later';
21
+ export function Setup() {
22
+ const [later, setLater] = useKept(LATER, false);
23
+ const [owed, setOwed] = useState(false);
24
+ useEffect(() => {
25
+ if (later || orgs().length)
26
+ return;
27
+ let live = true;
28
+ void firstRun(api()).then((r) => live && setOwed(r.pending));
29
+ return () => {
30
+ live = false;
31
+ };
32
+ }, [later]);
33
+ if (later || !owed)
34
+ return null;
35
+ return (_jsxs(XStack, { "data-slot": "setup", role: "region", "aria-label": "Set up Hanzo", items: "center", gap: "$2.5", px: "$3", py: "$2.5", rounded: "$4", borderWidth: 1, borderColor: "$borderColor", bg: "$edge", children: [_jsx(Sparkles, { size: 16, "aria-hidden": true }), _jsxs(YStack, { flex: 1, minW: 0, gap: "$0.5", children: [_jsx(SizableText, { size: "$2", color: "$ink", numberOfLines: 1, children: "Finish setting up Hanzo" }), _jsx(Text, { render: _jsx("a", { href: site(`/onboarding?next=${encodeURIComponent('/')}`) }), color: "var(--soft, var(--muted-foreground))", fontSize: "$1", textDecorationLine: "underline", self: "flex-start", children: "Set up" })] }), _jsx(XStack, { render: "button", "aria-label": "Not now", onPress: () => setLater(true), p: "$1", rounded: 999, hoverStyle: { bg: '$hover' }, children: _jsx(X, { size: 14, "aria-hidden": true }) })] }));
36
+ }
@@ -0,0 +1,3 @@
1
+ export declare function Share({ thread }: {
2
+ thread: string;
3
+ }): import("react").JSX.Element;
package/dist/Share.js ADDED
@@ -0,0 +1,130 @@
1
+ 'use client';
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ // The conversation header's Share control and the panel it opens.
4
+ //
5
+ // A link opens this ONE conversation, read only, as it stands when the link is
6
+ // made (lib/share.ts), to the people who open it signed in. The panel copies
7
+ // it, lists every live link with the people who opened it, removes any one of
8
+ // them, and revokes a link, which ends it for all of them. Recipients read and
9
+ // do not reply: a conversation belongs to the member who started it.
10
+ import { useCallback, useEffect, useId, useRef, useState } from 'react';
11
+ import { Check, Link2, Share2 } from 'lucide-react';
12
+ import { Box, Button, Text, XStack, YStack } from '@hanzo/ui';
13
+ import { hold, heldToken, link, Refused, shares } from './lib/share.js';
14
+ import { BAD } from './lib/mix.js';
15
+ /** How long "Link copied" stays. The same 1400 ms `Take` confirms for. */
16
+ const CONFIRM = 1400;
17
+ /** A date as the panel says it: "Sep 29, 2026". */
18
+ const day = (iso) => {
19
+ const d = new Date(iso);
20
+ return Number.isNaN(d.getTime()) ? '' : d.toLocaleDateString(undefined, { month: 'short', day: 'numeric', year: 'numeric' });
21
+ };
22
+ const said = (e) => (e instanceof Refused || e instanceof Error ? e.message : 'That did not work. Try again.');
23
+ export function Share({ thread }) {
24
+ const [open, setOpen] = useState(false);
25
+ const button = useRef(null);
26
+ const close = useCallback(() => {
27
+ setOpen(false);
28
+ button.current?.focus();
29
+ }, []);
30
+ return (_jsxs(Box, { position: "relative", children: [_jsx(Box, { ref: button, render: "button", onClick: () => setOpen((o) => !o), "aria-haspopup": "dialog", "aria-expanded": open, "aria-label": "Share this conversation", borderWidth: 0, bg: "transparent", p: "$1", rounded: "$2", hoverStyle: { bg: '$hover' }, children: _jsxs(XStack, { items: "center", gap: "$1", children: [_jsx(Share2, { size: 13, "aria-hidden": true }), _jsx(Text, { fontSize: "$2", color: "$ink", children: "Share" })] }) }), open ? _jsx(Panel, { thread: thread, onClose: close }) : null] }));
31
+ }
32
+ function Panel({ thread, onClose }) {
33
+ const heading = useId();
34
+ const box = useRef(null);
35
+ const [live, setLive] = useState(null);
36
+ const [busy, setBusy] = useState(null);
37
+ const [wrong, setWrong] = useState(null);
38
+ const [copied, setCopied] = useState(false);
39
+ useEffect(() => {
40
+ let on = true;
41
+ shares
42
+ .list(thread)
43
+ .then((rows) => on && setLive(rows))
44
+ .catch((e) => {
45
+ if (!on)
46
+ return;
47
+ setLive([]);
48
+ setWrong(said(e));
49
+ });
50
+ return () => {
51
+ on = false;
52
+ };
53
+ }, [thread]);
54
+ // Escape closes; a press outside closes; focus starts inside.
55
+ useEffect(() => {
56
+ box.current?.querySelector('button')?.focus();
57
+ const key = (e) => {
58
+ if (e.key === 'Escape')
59
+ onClose();
60
+ };
61
+ const press = (e) => {
62
+ if (box.current && !box.current.contains(e.target) && !e.target?.closest?.('[aria-haspopup="dialog"]'))
63
+ onClose();
64
+ };
65
+ document.addEventListener('keydown', key);
66
+ document.addEventListener('mousedown', press);
67
+ return () => {
68
+ document.removeEventListener('keydown', key);
69
+ document.removeEventListener('mousedown', press);
70
+ };
71
+ }, [onClose]);
72
+ useEffect(() => {
73
+ if (!copied)
74
+ return;
75
+ const t = setTimeout(() => setCopied(false), CONFIRM);
76
+ return () => clearTimeout(t);
77
+ }, [copied]);
78
+ const copy = async () => {
79
+ setBusy('copy');
80
+ setWrong(null);
81
+ try {
82
+ let got = heldToken(thread, live ?? []);
83
+ if (!got) {
84
+ const made = await shares.make(thread);
85
+ got = { id: made.share.id, token: made.token };
86
+ hold(thread, got);
87
+ setLive((rows) => [...(rows ?? []), made.share]);
88
+ }
89
+ await navigator.clipboard.writeText(link(got.token));
90
+ setCopied(true);
91
+ }
92
+ catch (e) {
93
+ setWrong(e instanceof DOMException ? 'The browser would not copy. Select the link and copy it by hand.' : said(e));
94
+ }
95
+ finally {
96
+ setBusy(null);
97
+ }
98
+ };
99
+ const unview = async (row, viewer) => {
100
+ setBusy(viewer.id);
101
+ setWrong(null);
102
+ try {
103
+ await shares.unview(thread, row.id, viewer.id);
104
+ setLive((rows) => (rows ?? []).map((r) => (r.id === row.id ? { ...r, viewers: r.viewers.filter((v) => v.id !== viewer.id) } : r)));
105
+ }
106
+ catch (e) {
107
+ setWrong(said(e));
108
+ }
109
+ finally {
110
+ setBusy(null);
111
+ }
112
+ };
113
+ const revoke = async (id) => {
114
+ setBusy(id);
115
+ setWrong(null);
116
+ try {
117
+ await shares.revoke(thread, id);
118
+ if (heldToken(thread, live ?? [])?.id === id)
119
+ hold(thread, null);
120
+ setLive((rows) => (rows ?? []).filter((r) => r.id !== id));
121
+ }
122
+ catch (e) {
123
+ setWrong(said(e));
124
+ }
125
+ finally {
126
+ setBusy(null);
127
+ }
128
+ };
129
+ return (_jsxs(YStack, { ref: box, role: "dialog", "aria-labelledby": heading, position: "absolute", r: 0, t: "100%", mt: "$2", z: "var(--z-dropdown)", width: 360, maxW: "calc(100vw - 32px)", p: "$4", gap: "$3", rounded: "$4", borderWidth: 1, borderColor: "$borderColor", bg: "$background", boxShadow: "0 16px 36px color-mix(in srgb, var(--pure-black) 30%, transparent)", children: [_jsx(Text, { id: heading, render: "h2", fontSize: "$4", fontWeight: "600", color: "$ink", children: "Share this chat" }), _jsx(Text, { fontSize: "$2", color: "$soft", lineHeight: "$3", children: "People who open the link signed in can read this chat as it is now, and you see who they are below. They cannot reply, and messages you send later are not shared." }), _jsxs(XStack, { items: "center", gap: "$2", children: [_jsx(Button, { size: "sm", onClick: () => void copy(), disabled: busy !== null || live === null, "aria-describedby": `${heading}-said`, children: _jsxs(XStack, { items: "center", gap: "$1.5", children: [copied ? _jsx(Check, { size: 14, "aria-hidden": true }) : _jsx(Link2, { size: 14, "aria-hidden": true }), _jsx(Text, { fontSize: "$2", color: "inherit", children: copied ? 'Link copied' : busy === 'copy' ? 'Making a link…' : 'Copy link' })] }) }), _jsx(Text, { fontSize: "$1", color: "$soft", children: "Read only" })] }), _jsx(Text, { id: `${heading}-said`, "aria-live": "polite", fontSize: "$1", color: "$soft", minH: 16, children: copied ? 'The link is on your clipboard.' : '' }), _jsxs(YStack, { gap: "$2", children: [_jsx(Text, { fontSize: "$1", color: "$soft", textTransform: "uppercase", children: "Who has access" }), _jsxs(XStack, { items: "center", justify: "space-between", children: [_jsx(Text, { fontSize: "$2", color: "$ink", children: "You" }), _jsx(Text, { fontSize: "$1", color: "$soft", children: "Owner" })] }), live === null ? (_jsx(Text, { fontSize: "$2", color: "$soft", children: "Reading links." })) : live.length === 0 ? (_jsx(Text, { fontSize: "$2", color: "$soft", children: "No links yet. Copy link makes one." })) : (_jsx(YStack, { render: "ul", gap: "$2", m: 0, p: 0, "aria-label": "Live links", children: live.map((row) => (_jsxs(YStack, { render: "li", gap: "$1.5", children: [_jsxs(XStack, { items: "center", justify: "space-between", gap: "$2", children: [_jsxs(YStack, { minW: 0, children: [_jsx(Text, { fontSize: "$2", color: "$ink", numberOfLines: 1, children: "Link" }), _jsxs(Text, { fontSize: "$1", color: "$soft", children: ["Read only", day(row.createdAt) ? ` · made ${day(row.createdAt)}` : ''] })] }), _jsx(Button, { size: "sm", variant: "outline", onClick: () => void revoke(row.id), disabled: busy !== null, "aria-label": `Revoke the link made ${day(row.createdAt) || 'earlier'}`, children: busy === row.id ? 'Revoking…' : 'Revoke' })] }), (row.viewers ?? []).length === 0 ? (_jsx(Text, { fontSize: "$1", color: "$soft", pl: "$3", children: "Nobody has opened it yet." })) : (_jsx(YStack, { render: "ul", gap: "$1", m: 0, pl: "$3", "aria-label": "Opened by", children: row.viewers.map((v) => (_jsxs(XStack, { render: "li", items: "center", justify: "space-between", gap: "$2", children: [_jsx(Text, { fontSize: "$2", color: "$ink", numberOfLines: 1, children: v.name || 'Someone signed in' }), _jsx(Button, { size: "sm", variant: "outline", onClick: () => void unview(row, v), disabled: busy !== null, "aria-label": `Remove ${v.name || 'this reader'}`, children: busy === v.id ? 'Removing…' : 'Remove' })] }, v.id))) }))] }, row.id))) }))] }), wrong ? (_jsx(Text, { role: "alert", fontSize: "$2", color: BAD, children: wrong })) : null] }));
130
+ }
@@ -0,0 +1 @@
1
+ export declare function Shared(): import("react").JSX.Element;