@hanzo/rooms 0.1.92 → 0.1.93

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.d.ts CHANGED
@@ -2,10 +2,9 @@ export declare function Chat(): import("react").JSX.Element;
2
2
  /**
3
3
  * The same conversation, embedded in a page that is not /chat.
4
4
  *
5
- * R8: there were TWO live chat surfaces with different consent semantics — this
6
- * one asks at SEND and fails closed, the other gated on ARRIVAL through a
7
- * separate copy of the consent code. Two answers to one question is how they
8
- * drift, so /models mounts this and the second surface is gone. It carries no
5
+ * R8: there were TWO live chat surfaces, so /models mounts this one and the
6
+ * second is gone. The data-share choice is made once at onboarding; this room
7
+ * does not ask again when a message is sent. It carries no
9
8
  * heading of its own: the host page already has one, and a second `<h1>` is the
10
9
  * defect that took the landings off /chat and /dev.
11
10
  *
@@ -32,4 +31,3 @@ export declare function refusalOf(error: Error): {
32
31
  visitor: boolean;
33
32
  provider: boolean;
34
33
  };
35
- /** The agreement free asks for, in @hanzo/ai's words so every surface says one thing. */
package/dist/Chat.js CHANGED
@@ -17,9 +17,9 @@ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-run
17
17
  // the free pool, so what changes at sign-in is the menu, not the room.
18
18
  //
19
19
  // The FREE lane is about PRICE, not identity: a free turn spends no balance and
20
- // is data-shared in exchange, which is what the consent block asks about, at
21
- // SEND rather than on arrival — consent is owed when someone sends, not when
22
- // they walk in. Signing in is what buys the paid models and a history that
20
+ // is data-shared in exchange. That choice is made once, at onboarding, where
21
+ // the terms are linked. The line under the composer says so for as long as a
22
+ // free model answers. Signing in buys the paid models and a history that
23
23
  // follows you; it is never the price of asking a question.
24
24
  //
25
25
  // This file used to branch on `isAuthenticated` and hand a visitor a composer
@@ -31,7 +31,7 @@ import { useCallback, useContext, useEffect, useMemo, useRef, useState } from "r
31
31
  import { billed, detail, familyOf, planRequired, plainly, refused } from "./failure.js";
32
32
  import { Prose } from "./Prose.js";
33
33
  import { useAgent, useAi, useChat, } from "@hanzo/ai/react";
34
- import { freeCopy, grantConsent, hasConsent, isFree, } from "@hanzo/ai";
34
+ import { isFree } from "@hanzo/ai";
35
35
  import { ASK, Chat as ChatSurface, Failure, Parts, words } from "@hanzo/ui/chat";
36
36
  import { carriedIn, carrying, compose, partsOf, said as told, weigh } from "./lib/attach.js";
37
37
  import { download, dropped, fileOf, pct, readable, refOf, retrieve } from "./lib/files.js";
@@ -69,10 +69,8 @@ import { checkoutUrl } from './lib/pay.js';
69
69
  import { Upgrade, useFree } from './Upgrade.js';
70
70
  import { enter, LOGIN } from './lib/destination.js';
71
71
  import { first } from './lib/first.js';
72
- import { BAD, mix } from "./lib/mix.js";
72
+ import { BAD } from "./lib/mix.js";
73
73
  import { brand, site } from './where.js';
74
- /** The veil under the free lane's consent sheet. */
75
- const SCRIM = mix("var(--pure-black)", 72, "srgb");
76
74
  // NO REASONING PANEL HERE. Every assistant turn opened a "Thinking" disclosure
77
75
  // listing seven steps — "Planning CSS extraction for chat styles", "Designing
78
76
  // responsive top bar grid layout" — typed into this file as a literal array.
@@ -89,24 +87,6 @@ const SCRIM = mix("var(--pure-black)", 72, "srgb");
89
87
  * room has always read at, so an untouched setting changes nothing.
90
88
  */
91
89
  const MEASURE = "var(--container-prose, 48rem)";
92
- /**
93
- * The store, or null when the browser will not give us one.
94
- *
95
- * Reading `window.localStorage` THROWS `SecurityError` where site data is
96
- * blocked — third-party contexts, Safari's Block All Cookies, a hardened
97
- * profile — and this is called from inside a send handler, so an unguarded
98
- * access takes the send down rather than the storage.
99
- */
100
- const store = () => {
101
- if (typeof window === "undefined")
102
- return null;
103
- try {
104
- return window.localStorage;
105
- }
106
- catch {
107
- return null;
108
- }
109
- };
110
90
  /**
111
91
  * The text this screen arrived with, TYPED INTO THE COMPOSER — never sent.
112
92
  *
@@ -182,8 +162,8 @@ export function Chat() {
182
162
  // mounted under the provider the shell already put above it.
183
163
  //
184
164
  // ONE room, for everyone. `anonymous` changes which models are on offer and
185
- // nothing else — the composer, the consent and the transcript are the same
186
- // components either way, because a visitor is having the same conversation.
165
+ // nothing else — the composer and the transcript are the same components
166
+ // either way, because a visitor is having the same conversation.
187
167
  //
188
168
  // The conversations are NOT drawn here. `Threads` is the list and the shell
189
169
  // owns the column it sits in — one sidebar for six rooms, already built, and a
@@ -194,10 +174,9 @@ export function Chat() {
194
174
  /**
195
175
  * The same conversation, embedded in a page that is not /chat.
196
176
  *
197
- * R8: there were TWO live chat surfaces with different consent semantics — this
198
- * one asks at SEND and fails closed, the other gated on ARRIVAL through a
199
- * separate copy of the consent code. Two answers to one question is how they
200
- * drift, so /models mounts this and the second surface is gone. It carries no
177
+ * R8: there were TWO live chat surfaces, so /models mounts this one and the
178
+ * second is gone. The data-share choice is made once at onboarding; this room
179
+ * does not ask again when a message is sent. It carries no
201
180
  * heading of its own: the host page already has one, and a second `<h1>` is the
202
181
  * defect that took the landings off /chat and /dev.
203
182
  *
@@ -476,9 +455,9 @@ function Plan({ onPress }) {
476
455
  *
477
456
  * `anonymous` opens on the free pool and offers only what the free pool can
478
457
  * answer, because listing a paid model to someone with no balance is an offer
479
- * that fails at send. Everything else — the transcript, the composer, the
480
- * consent — is identical, which is the point: signing in later changes the
481
- * models on the menu, not the room.
458
+ * that fails at send. Everything else — the transcript, the composer — is
459
+ * identical, which is the point: signing in later changes the models on the
460
+ * menu, not the room.
482
461
  */
483
462
  function Thread({ placeholder = message(), initial, anonymous = false, heading, }) {
484
463
  /**
@@ -487,16 +466,14 @@ function Thread({ placeholder = message(), initial, anonymous = false, heading,
487
466
  * `useState(anonymous ? FREE : ENSO)` read the flag ONCE, on the first render
488
467
  * — which is the render before hydration settles, where `anonymous` is always
489
468
  * false. So every visitor opened on ENSO while the composer beside them said
490
- * "Enso Free", `isFree(model)` was false, and the two things that hang off it
491
- * both went quiet: the data-sharing notice never appeared, and the consent
492
- * block never asked. A visitor was sending on the paid pool having agreed to
493
- * nothing, because a state initialiser cannot see a value that arrives later.
469
+ * "Enso Free", and `isFree(model)` was false, so the data-sharing notice
470
+ * never appeared. A visitor was sending on the paid pool, because a state
471
+ * initialiser cannot see a value that arrives later.
494
472
  *
495
473
  * A CHOICE, or the identity. `null` is "nobody has chosen", which is a
496
474
  * different fact from either model and the only one that can defer to a flag
497
475
  * that settles after mount. Picking from the menu still wins, permanently.
498
476
  */
499
- const [asking, setAsking] = useState(null);
500
477
  // THE MODEL AND THE EFFORT are the one choice Chat and Dev share
501
478
  // (@hanzo/build `useMind`): the catalog from `GET /v1/models` and what the
502
479
  // reader kept, changed from the composer's chip or from Settings.
@@ -1138,17 +1115,9 @@ function Thread({ placeholder = message(), initial, anonymous = false, heading,
1138
1115
  * no longer allowed to draw. `:focus-within` would say this in CSS; the
1139
1116
  * stylesheet that would hold it cannot reach past gui's own rule, so the
1140
1117
  * state is read where the event is. */
1141
- const guarded = useCallback((text) => {
1142
- const s = store();
1143
- // FAILS CLOSED. This read `s && !hasConsent(s)`, so a browser that gives
1144
- // us no store answered "no consent needed" and sent a data-shared turn
1145
- // having asked nobody. No store means no record, and no record means ask.
1146
- if (isFree(model) && (!s || !hasConsent(s))) {
1147
- setAsking(text);
1148
- return;
1149
- }
1150
- speak(text);
1151
- }, [model, speak]);
1118
+ // A free send goes. The data-share choice was made at onboarding, once,
1119
+ // and the line under the composer still says what a free model does.
1120
+ const guarded = speak;
1152
1121
  /**
1153
1122
  * The draft and the files, sent as one turn.
1154
1123
  *
@@ -1317,25 +1286,6 @@ function Thread({ placeholder = message(), initial, anonymous = false, heading,
1317
1286
  enter(LOGIN);
1318
1287
  }, children: "Sign in" })) : lift ? (_jsx(Button, { size: "sm", variant: "outline", onClick: () => setAsk({ ...lift, model: nameOf(model, listed) }), children: "Upgrade" })) : how?.visitor ? (_jsx(Button, { size: "sm", variant: "outline", onClick: () => enter(), children: "Sign in to keep going" })) : (_jsx(Anchor, { href: checkoutUrl(), children: _jsx(Button, { size: "sm", variant: "outline", children: "Add credit" }) })) })) : null] }) }));
1319
1288
  };
1320
- // The agreement is asked OVER the room, not instead of it. Returning the
1321
- // consent on its own replaced the whole pane: the question the reader had
1322
- // just typed left the screen, and the first thing a stranger saw of the
1323
- // product was a page about training data. The room stays; the sheet sits on
1324
- // top of it, and the question is still there behind it.
1325
- const consent = asking === null ? null : (_jsx(Consent, { question: told(asking).text, onAgree: () => {
1326
- const s = store();
1327
- // Nothing to persist into is not a reason to refuse the turn the
1328
- // reader just agreed to; it means the question is asked again next
1329
- // time, which is the honest cost of a browser that stores nothing.
1330
- if (s)
1331
- grantConsent(s);
1332
- const held = asking;
1333
- setAsking(null);
1334
- if (held)
1335
- speak(held);
1336
- }, onCancel: () => {
1337
- setAsking(null);
1338
- } }));
1339
1289
  // Dev, from the Summary's Create file or site: a move inside the app where the
1340
1290
  // host's app reads the path (hanzo.ai), else Dev on hanzo.ai, as the shell's
1341
1291
  // Dev place goes from a host without it (hanzo.team).
@@ -1573,9 +1523,5 @@ function Thread({ placeholder = message(), initial, anonymous = false, heading,
1573
1523
  empty: heading === null ? undefined : (_jsxs(YStack, { "data-slot": "opening", items: "center", width: "100%", pt: "$6", pb: "$2", ...(media.sm ? null : { flex: 1, justify: "flex-end" }), children: [room.length > 1 ? (_jsx(XStack, { items: "center", justify: "center", mb: "$4", children: room.map((name, i) => (_jsx(Box, { ml: i ? -16 : 0, rounded: 40, borderWidth: 2, borderColor: "$background", z: room.length - i, children: _jsx(Face, { src: faces[name]?.avatar, emoji: faces[name]?.emoji, name: name, size: 72 }) }, name))) })) : portrait ? (_jsx(Box, { width: 160, height: 160, rounded: 24, overflow: "hidden", borderWidth: 2, borderColor: "var(--white-15)", boxShadow: "0 10px 24px color-mix(in srgb, var(--pure-black) 70%, transparent)", mb: "$4", bg: "var(--neutral-900)", children: _jsx(View, { render: _jsx("img", { src: portrait, alt: withWhom }), width: "100%", height: "100%", objectFit: "cover" }) })) : null, room.length === 1 && !portrait ? (_jsx(Box, { mb: "$3", children: _jsx(Face, { emoji: mate?.emoji ?? persona?.emoji, name: withWhom, size: 72 }) })) : null, _jsx(Text, { render: "h1", fontSize: "$7", lineHeight: "$7", fontWeight: "700", color: "$ink", text: "center", mb: "$1", children: join(room) || heading || (firstName ? `What’s up, ${firstName}?` : "What’s up?") }), room.length === 1 && (mate?.description ?? persona?.description) ? (_jsx(Text, { fontSize: "$3", color: "$soft", text: "center", maxW: 500, mb: "$4", lineHeight: "$4", children: mate?.description ?? persona?.description })) : null] })),
1574
1524
  // The transcript reads at the same measure. `Thread` defaults to 768
1575
1525
  // itself; handing it the column here is what lets Width reach it.
1576
- column: { maxW: MEASURE }, composer: prompt({ ...composing, field: mention.field }, mention.state) }), _jsx(AgentMaker, {}), carry.dialog, _jsx(XStack, { items: "center", justify: "center", py: "$1", px: "$3", gap: "$2", children: isFree(model) ? (_jsxs(Text, { fontSize: "$1", color: "$soft", children: ["Free chat is data-shared to train models.", " ", _jsx(Anchor, { href: site("/legal/privacy"), fontSize: "$1", color: "$ink", children: "Privacy" })] })) : (_jsxs(_Fragment, { children: [_jsx(Anchor, { href: site("/legal/terms"), fontSize: "$1", color: "$soft", children: "Terms" }), _jsx(Text, { fontSize: "$1", color: "$soft", children: "\u00B7" }), _jsx(Anchor, { href: site("/legal/privacy"), fontSize: "$1", color: "$soft", children: "Privacy" })] })) }), consent, _jsx(Upgrade, { ask: ask, onClose: () => setAsk(null) })] }));
1577
- }
1578
- /** The agreement free asks for, in @hanzo/ai's words so every surface says one thing. */
1579
- function Consent({ question, onAgree, onCancel, }) {
1580
- return (_jsx(YStack, { position: "absolute", t: 0, l: 0, r: 0, b: 0, items: "center", justify: "center", p: "$4", z: 40, bg: SCRIM, children: _jsxs(YStack, { width: "100%", maxW: 512, px: "$5", py: "$5", gap: "$4", rounded: "$4", borderWidth: 1, borderColor: "$borderColor", bg: "$background", children: [_jsx(Text, { render: "h2", fontSize: "$6", fontWeight: "500", color: "$ink", children: freeCopy.consentTitle }), question ? (_jsxs(Text, { fontSize: "$3", color: "$soft", numberOfLines: 2, children: ["Your question is waiting: \u201C", question, "\u201D"] })) : null, _jsx(Text, { fontSize: "$4", color: "$soft", children: freeCopy.consentBody }), _jsx(YStack, { gap: "$2", children: freeCopy.consentPoints.map((point) => (_jsx(Text, { fontSize: "$3", color: "$soft", children: point }, point))) }), _jsxs(XStack, { gap: "$3", flexWrap: "wrap", children: [_jsx(Button, { onClick: onAgree, children: freeCopy.consentAgreeCta }), _jsx(Button, { variant: "outline", onClick: onCancel, children: freeCopy.consentCancelCta })] }), _jsxs(Text, { fontSize: "$2", color: "$soft", children: [freeCopy.termsText, " ", _jsx(Anchor, { href: site("/legal/terms"), children: "Terms" }), " · ", _jsx(Anchor, { href: site("/legal/privacy"), children: "Privacy" })] })] }) }));
1526
+ column: { maxW: MEASURE }, composer: prompt({ ...composing, field: mention.field }, mention.state) }), _jsx(AgentMaker, {}), carry.dialog, _jsx(XStack, { items: "center", justify: "center", py: "$1", px: "$3", gap: "$2", children: isFree(model) ? (_jsxs(Text, { fontSize: "$1", color: "$soft", children: ["Free chat is data-shared to train models.", " ", _jsx(Anchor, { href: site("/legal/privacy"), fontSize: "$1", color: "$ink", children: "Privacy" })] })) : (_jsxs(_Fragment, { children: [_jsx(Anchor, { href: site("/legal/terms"), fontSize: "$1", color: "$soft", children: "Terms" }), _jsx(Text, { fontSize: "$1", color: "$soft", children: "\u00B7" }), _jsx(Anchor, { href: site("/legal/privacy"), fontSize: "$1", color: "$soft", children: "Privacy" })] })) }), _jsx(Upgrade, { ask: ask, onClose: () => setAsk(null) })] }));
1581
1527
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hanzo/rooms",
3
- "version": "0.1.92",
3
+ "version": "0.1.93",
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",