@hanzo/ui 8.1.4 → 8.1.5

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 (73) hide show
  1. package/dist/chat/Code.cjs +2 -2
  2. package/dist/chat/Code.d.ts +16 -3
  3. package/dist/chat/Code.d.ts.map +1 -1
  4. package/dist/chat/Code.js +2 -2
  5. package/dist/chat/Code.js.map +1 -1
  6. package/dist/chat/Composer.cjs +16 -10
  7. package/dist/chat/Composer.d.ts +59 -3
  8. package/dist/chat/Composer.d.ts.map +1 -1
  9. package/dist/chat/Composer.js +16 -10
  10. package/dist/chat/Composer.js.map +1 -1
  11. package/dist/chat/Failure.cjs +2 -2
  12. package/dist/chat/Failure.d.ts +29 -3
  13. package/dist/chat/Failure.d.ts.map +1 -1
  14. package/dist/chat/Failure.js +2 -2
  15. package/dist/chat/Failure.js.map +1 -1
  16. package/dist/chat/Header.cjs +2 -2
  17. package/dist/chat/Header.d.ts +20 -3
  18. package/dist/chat/Header.d.ts.map +1 -1
  19. package/dist/chat/Header.js +2 -2
  20. package/dist/chat/Header.js.map +1 -1
  21. package/dist/chat/Message.cjs +10 -4
  22. package/dist/chat/Message.d.ts +42 -3
  23. package/dist/chat/Message.d.ts.map +1 -1
  24. package/dist/chat/Message.js +10 -4
  25. package/dist/chat/Message.js.map +1 -1
  26. package/dist/chat/Sources.cjs +33 -10
  27. package/dist/chat/Sources.d.ts +46 -6
  28. package/dist/chat/Sources.d.ts.map +1 -1
  29. package/dist/chat/Sources.js +32 -9
  30. package/dist/chat/Sources.js.map +1 -1
  31. package/dist/chat/Step.cjs +2 -2
  32. package/dist/chat/Step.d.ts +4 -3
  33. package/dist/chat/Step.d.ts.map +1 -1
  34. package/dist/chat/Step.js +2 -2
  35. package/dist/chat/Step.js.map +1 -1
  36. package/dist/chat/Thread.cjs +38 -13
  37. package/dist/chat/Thread.d.ts +55 -3
  38. package/dist/chat/Thread.d.ts.map +1 -1
  39. package/dist/chat/Thread.js +38 -13
  40. package/dist/chat/Thread.js.map +1 -1
  41. package/dist/chat/index.cjs +19 -2
  42. package/dist/chat/index.d.ts +18 -1
  43. package/dist/chat/index.d.ts.map +1 -1
  44. package/dist/chat/index.js +18 -1
  45. package/dist/chat/index.js.map +1 -1
  46. package/dist/chat/pure.cjs +41 -0
  47. package/dist/chat/pure.d.ts +3 -0
  48. package/dist/chat/pure.d.ts.map +1 -0
  49. package/dist/chat/pure.js +35 -0
  50. package/dist/chat/pure.js.map +1 -0
  51. package/dist/chat/send.cjs +10 -1
  52. package/dist/chat/send.d.ts +9 -0
  53. package/dist/chat/send.d.ts.map +1 -1
  54. package/dist/chat/send.js +10 -1
  55. package/dist/chat/send.js.map +1 -1
  56. package/dist/chat/stick.cjs +1 -1
  57. package/dist/chat/stick.js +1 -1
  58. package/dist/gallery.cjs +5 -4
  59. package/dist/gallery.d.ts.map +1 -1
  60. package/dist/gallery.js +5 -4
  61. package/dist/gallery.js.map +1 -1
  62. package/dist/product/CopyButton.cjs +2 -2
  63. package/dist/product/CopyButton.d.ts +12 -2
  64. package/dist/product/CopyButton.d.ts.map +1 -1
  65. package/dist/product/CopyButton.js +2 -2
  66. package/dist/product/CopyButton.js.map +1 -1
  67. package/dist/product/pure.cjs +12 -1
  68. package/dist/product/pure.d.ts +2 -0
  69. package/dist/product/pure.d.ts.map +1 -1
  70. package/dist/product/pure.js +7 -0
  71. package/dist/product/pure.js.map +1 -1
  72. package/dist/styles.css +14 -2
  73. package/package.json +7 -1
@@ -1 +1 @@
1
- {"version":3,"file":"Message.js","sourceRoot":"","sources":["../../src/chat/Message.tsx"],"names":[],"mappings":"AAAA,YAAY,CAAA;;AAEZ;;;;;;;;;;;;;;;;;GAiBG;AACH,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,YAAY,CAAA;AAGjD,OAAO,EAAE,GAAG,EAAE,MAAM,qBAAqB,CAAA;AACzC,OAAO,EAAE,IAAI,EAAE,MAAM,sBAAsB,CAAA;AAI3C;;;;;;;;;;;;;;GAcG;AACH,MAAM,UAAU,KAAK;IACnB,OAAO,KAAC,MAAM,OAAK,IAAI,CAAC,OAAO,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,MAAM,EAAE,EAAE,EAAE,OAAO,EAAE,CAAC,EAAE,EAAE,EAAC,UAAU,wBAAe,CAAA;AAClG,CAAC;AAaD,MAAM,UAAU,OAAO,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,OAAO,EAAE,IAAI,GAAG,KAAK,EAAE,IAAI,EAAgB;IACnF,IAAI,IAAI,KAAK,QAAQ;QACnB,OAAO,CACL,KAAC,MAAM,OAAK,IAAI,CAAC,SAAS,CAAC,eAAY,QAAQ,EAAC,KAAK,EAAC,MAAM,EAAC,OAAO,EAAC,QAAQ,EAAC,EAAE,EAAC,IAAI,YACnF,KAAC,IAAI,IAAC,QAAQ,EAAC,IAAI,EAAC,KAAK,EAAC,UAAU,EAAC,IAAI,EAAC,QAAQ,YAC/C,QAAQ,GACJ,GACA,CACV,CAAA;IAEH,MAAM,IAAI,GAAG,IAAI,KAAK,MAAM,CAAA;IAC5B,OAAO,CACL,MAAC,MAAM,OACD,IAAI,CAAC,SAAS,CAAC,eACR,IAAI,EACf,KAAK,EAAC,MAAM,EACZ,OAAO,EAAE,IAAI,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,YAAY,EACzC,GAAG,EAAC,IAAI,aAEP,IAAI,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,KAAC,MAAM,IAAC,EAAE,EAAC,IAAI,YAAE,IAAI,GAAU,CAAC,CAAC,CAAC,IAAI,EACvD,MAAC,MAAM,OACD,IAAI,CAAC,cAAc,CAAC,iBACX,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,SAAS,EACtC,GAAG,EAAC,IAAI;gBACR,oEAAoE;gBACpE,0DAA0D;gBAC1D,8DAA8D;gBAC9D,uDAAuD;gBACvD,mEAAmE;gBACnE,YAAY;gBACZ,IAAI,EAAE,IAAI,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,EAC3B,IAAI,EAAE,IAAI,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,KACtB,CAAC,IAAI,IAAI;oBACX,EAAE,EAAE,SAAS;oBACb,OAAO,EAAE,IAAI;oBACb,EAAE,EAAE,IAAI;oBACR,EAAE,EAAE,IAAI;iBACT,CAAC,aAED,GAAG,CAAC,QAAQ,CAAC,EACb,IAAI,CAAC,CAAC,CAAC,KAAC,KAAK,KAAG,CAAC,CAAC,CAAC,IAAI,EACvB,OAAO,CAAC,CAAC,CAAC,KAAC,MAAM,IAAC,GAAG,EAAC,IAAI,EAAC,KAAK,EAAC,QAAQ,YAAE,OAAO,GAAU,CAAC,CAAC,CAAC,IAAI,IAC7D,IACF,CACV,CAAA;AACH,CAAC"}
1
+ {"version":3,"file":"Message.js","sourceRoot":"","sources":["../../src/chat/Message.tsx"],"names":[],"mappings":"AAAA,YAAY,CAAA;;AAEZ;;;;;;;;;;;;;;;;;GAiBG;AACH,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,YAAY,CAAA;AAGjD,OAAO,EAAE,GAAG,EAAE,MAAM,qBAAqB,CAAA;AACzC,OAAO,EAAE,IAAI,EAAE,MAAM,sBAAsB,CAAA;AAa3C;;;;;;;;;;;;;;GAcG;AACH,MAAM,UAAU,KAAK;IACnB,OAAO,KAAC,MAAM,OAAK,IAAI,CAAC,OAAO,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,MAAM,EAAE,EAAE,EAAE,OAAO,EAAE,CAAC,EAAE,EAAE,EAAC,UAAU,wBAAe,CAAA;AAClG,CAAC;AAwBD,MAAM,UAAU,OAAO,CAAC,EACtB,IAAI,EACJ,QAAQ,EACR,OAAO,EACP,IAAI,GAAG,KAAK,EACZ,IAAI,EACJ,IAAI,EACJ,GAAG,KAAK,EACK;IACb,IAAI,IAAI,KAAK,QAAQ;QACnB,OAAO,CACL,KAAC,MAAM,OACD,IAAI,CAAC,SAAS,CAAC,eACT,QAAQ,EAClB,KAAK,EAAC,MAAM,EACZ,OAAO,EAAC,QAAQ,EAChB,EAAE,EAAC,IAAI,KACH,KAAK,YAET,KAAC,IAAI,IAAC,QAAQ,EAAC,IAAI,EAAC,KAAK,EAAC,UAAU,EAAC,IAAI,EAAC,QAAQ,YAC/C,QAAQ,GACJ,GACA,CACV,CAAA;IAEH,MAAM,IAAI,GAAG,IAAI,KAAK,MAAM,CAAA;IAC5B,OAAO,CACL,MAAC,MAAM,OACD,IAAI,CAAC,SAAS,CAAC,eACR,IAAI,EACf,KAAK,EAAC,MAAM,EACZ,OAAO,EAAE,IAAI,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,YAAY,EACzC,GAAG,EAAC,IAAI;QACR,oEAAoE;QACpE,wEAAwE;QACxE,yEAAyE;QACzE,yEAAyE;QACzE,oEAAoE;QACpE,KAAK,WACD,KAAK,aAER,IAAI,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,KAAC,MAAM,IAAC,EAAE,EAAC,IAAI,YAAE,IAAI,GAAU,CAAC,CAAC,CAAC,IAAI,EACvD,MAAC,MAAM,OACD,IAAI,CAAC,cAAc,CAAC,iBACX,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,SAAS,EACtC,GAAG,EAAC,IAAI;gBACR,oEAAoE;gBACpE,0DAA0D;gBAC1D,8DAA8D;gBAC9D,uDAAuD;gBACvD,mEAAmE;gBACnE,YAAY;gBACZ,IAAI,EAAE,IAAI,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,EAC3B,IAAI,EAAE,IAAI,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,KACtB,CAAC,IAAI,IAAI;oBACX,EAAE,EAAE,SAAS;oBACb,OAAO,EAAE,IAAI;oBACb,EAAE,EAAE,IAAI;oBACR,EAAE,EAAE,IAAI;iBACT,CAAC,KACE,IAAI,aAEP,GAAG,CAAC,QAAQ,CAAC,EACb,IAAI,CAAC,CAAC,CAAC,KAAC,KAAK,KAAG,CAAC,CAAC,CAAC,IAAI,EACvB,OAAO,CAAC,CAAC,CAAC,KAAC,MAAM,IAAC,GAAG,EAAC,IAAI,EAAC,KAAK,EAAC,QAAQ,YAAE,OAAO,GAAU,CAAC,CAAC,CAAC,IAAI,IAC7D,IACF,CACV,CAAA;AACH,CAAC"}
@@ -2,24 +2,47 @@
2
2
  'use client';
3
3
  Object.defineProperty(exports, "__esModule", { value: true });
4
4
  exports.Sources = Sources;
5
- exports.SourceChip = SourceChip;
5
+ exports.SourceCard = SourceCard;
6
6
  const jsx_runtime_1 = require("react/jsx-runtime");
7
7
  /**
8
- * Sources — the citation grid for a turn.
8
+ * Sources — the citations for a turn, as a strip of cards.
9
9
  *
10
- * A dense grid of marks rather than a list of titles. Sources are scanned for
11
- * "which of these do I already trust", and twenty full-width titles would push
12
- * the rail well past a screen. Each mark keeps its title as accessible name and
13
- * tooltip, so the information is reordered, not discarded.
10
+ * This used to be a wrapping grid of 28px marks with the title reachable only
11
+ * as a tooltip, and the argument for it was density: sources are scanned for
12
+ * "which of these do I already trust", and twenty full-width titles push the
13
+ * rail past a screen. The density argument is right. Hiding the HOST was not,
14
+ * because the host is the answer to that exact question — a favicon is sixteen
15
+ * pixels of ambiguity and `nytimes.com` is a verdict — and neither surface that
16
+ * ships citations hides it. hanzo/chat's `SourceItem` (`components/Web/
17
+ * Sources.tsx`) draws favicon, domain, title and a snippet; the extension's
18
+ * `answer/Source.tsx:9-26` draws a number, a favicon, the host and a
19
+ * three-line-clamped title, and its own comment records where that came from:
20
+ * "Source card — chat AnswerView: 160px, number pill first, favicon, host,
21
+ * 3-line title".
22
+ *
23
+ * So density is kept by scrolling sideways rather than by discarding the text —
24
+ * the strip is one row tall whatever the count, which is what the grid was
25
+ * really buying. Both surfaces already scroll: `.ae-sources` is
26
+ * `display:flex; overflow-x:auto` at `newtab.css:260`.
27
+ *
28
+ * The number is not decoration. Both surfaces number the cards to match a
29
+ * marker in the prose, and the marker itself is not here: chat mints it with a
30
+ * remark plugin over private-use codepoints (`components/Web/plugin.ts`), which
31
+ * belongs to the markdown pipeline that stays with the surface.
14
32
  */
15
33
  const gui_1 = require("@hanzo/gui");
16
34
  const slot_1 = require("../backends/gui/slot.cjs");
17
- function Sources({ sources, title = 'Sources', onOpen }) {
35
+ /** Card width, px. The extension's is 168 and it cites chat's 160; the point of
36
+ * a fixed width is that a strip of them scans as a row rather than as ragged
37
+ * columns, so it is one number and not a range. */
38
+ const CARD = 168;
39
+ function Sources({ sources, title = 'Sources', onOpen, ...props }) {
18
40
  if (sources.length === 0)
19
41
  return null;
20
- return ((0, jsx_runtime_1.jsxs)(gui_1.YStack, { ...(0, slot_1.slot)('sources'), gap: "$2", p: "$3", rounded: "$4", borderWidth: 1, borderColor: "$borderColor", children: [(0, jsx_runtime_1.jsxs)(gui_1.XStack, { items: "center", gap: "$1.5", children: [(0, jsx_runtime_1.jsx)(gui_1.SizableText, { size: "$1", fontWeight: "500", color: "$color10", children: title }), (0, jsx_runtime_1.jsx)(gui_1.SizableText, { size: "$1", color: "$color9", children: sources.length })] }), (0, jsx_runtime_1.jsx)(gui_1.XStack, { flexWrap: "wrap", gap: "$1.5", children: sources.map((source) => ((0, jsx_runtime_1.jsx)(SourceChip, { source: source, onOpen: onOpen }, source.id))) })] }));
42
+ return ((0, jsx_runtime_1.jsxs)(gui_1.YStack, { ...(0, slot_1.slot)('sources'), gap: "$2", ...props, children: [(0, jsx_runtime_1.jsxs)(gui_1.XStack, { items: "center", gap: "$1.5", children: [(0, jsx_runtime_1.jsx)(gui_1.SizableText, { size: "$1", fontWeight: "500", color: "$color10", children: title }), (0, jsx_runtime_1.jsx)(gui_1.SizableText, { size: "$1", color: "$color9", children: sources.length })] }), (0, jsx_runtime_1.jsx)(gui_1.XStack, { ...(0, slot_1.slot)('sources-strip'), overflow: "scroll", contain: "content", gap: "$2", pb: "$1", children: sources.map((source, i) => ((0, jsx_runtime_1.jsx)(SourceCard, { source: source, index: i + 1, onOpen: onOpen }, source.id))) })] }));
21
43
  }
22
- function SourceChip({ source, onOpen }) {
44
+ function SourceCard({ source, index, onOpen, ...props }) {
23
45
  const press = onOpen ? () => onOpen(source) : undefined;
24
- return ((0, jsx_runtime_1.jsx)(gui_1.XStack, { ...(0, slot_1.slot)('source-chip'), width: 28, height: 28, items: "center", justify: "center", rounded: "$3", borderWidth: 1, borderColor: "$borderColor", bg: "$color3", overflow: "hidden", cursor: press ? 'pointer' : undefined, onPress: press, role: press ? 'button' : undefined, tabIndex: press ? 0 : undefined, "aria-label": source.title, ...(0, slot_1.tip)(source.title), hoverStyle: press ? { borderColor: '$color8' } : undefined, pressStyle: press ? { bg: '$color5' } : undefined, children: source.icon ?? ((0, jsx_runtime_1.jsx)(gui_1.SizableText, { size: "$1", color: "$color11", children: source.title.charAt(0).toUpperCase() })) }));
46
+ const label = source.host ?? source.title;
47
+ return ((0, jsx_runtime_1.jsxs)(gui_1.YStack, { ...(0, slot_1.slot)('source-card'), width: CARD, shrink: 0, gap: "$1.5", p: "$2.5", rounded: "$4", borderWidth: 1, borderColor: "$borderColor", bg: "$color2", cursor: press ? 'pointer' : undefined, onPress: press, role: press ? 'button' : undefined, tabIndex: press ? 0 : undefined, "aria-label": source.title, ...(0, slot_1.tip)(source.title), hoverStyle: press ? { borderColor: '$color8' } : undefined, pressStyle: press ? { bg: '$color4' } : undefined, ...props, children: [(0, jsx_runtime_1.jsxs)(gui_1.XStack, { items: "center", gap: "$1.5", children: [index != null ? ((0, jsx_runtime_1.jsx)(gui_1.SizableText, { size: "$1", color: "$color9", children: index })) : null, (0, jsx_runtime_1.jsx)(gui_1.XStack, { width: 14, height: 14, shrink: 0, items: "center", justify: "center", rounded: "$1", overflow: "hidden", bg: "$color4", children: source.icon ?? ((0, jsx_runtime_1.jsx)(gui_1.SizableText, { size: "$1", color: "$color11", children: label.charAt(0).toUpperCase() })) }), (0, jsx_runtime_1.jsx)(gui_1.SizableText, { size: "$1", color: "$color10", numberOfLines: 1, children: label })] }), (0, jsx_runtime_1.jsx)(gui_1.SizableText, { size: "$1", color: "$color12", numberOfLines: 3, children: source.title })] }));
25
48
  }
@@ -1,23 +1,63 @@
1
- import type { ReactNode } from 'react';
1
+ /**
2
+ * Sources — the citations for a turn, as a strip of cards.
3
+ *
4
+ * This used to be a wrapping grid of 28px marks with the title reachable only
5
+ * as a tooltip, and the argument for it was density: sources are scanned for
6
+ * "which of these do I already trust", and twenty full-width titles push the
7
+ * rail past a screen. The density argument is right. Hiding the HOST was not,
8
+ * because the host is the answer to that exact question — a favicon is sixteen
9
+ * pixels of ambiguity and `nytimes.com` is a verdict — and neither surface that
10
+ * ships citations hides it. hanzo/chat's `SourceItem` (`components/Web/
11
+ * Sources.tsx`) draws favicon, domain, title and a snippet; the extension's
12
+ * `answer/Source.tsx:9-26` draws a number, a favicon, the host and a
13
+ * three-line-clamped title, and its own comment records where that came from:
14
+ * "Source card — chat AnswerView: 160px, number pill first, favicon, host,
15
+ * 3-line title".
16
+ *
17
+ * So density is kept by scrolling sideways rather than by discarding the text —
18
+ * the strip is one row tall whatever the count, which is what the grid was
19
+ * really buying. Both surfaces already scroll: `.ae-sources` is
20
+ * `display:flex; overflow-x:auto` at `newtab.css:260`.
21
+ *
22
+ * The number is not decoration. Both surfaces number the cards to match a
23
+ * marker in the prose, and the marker itself is not here: chat mints it with a
24
+ * remark plugin over private-use codepoints (`components/Web/plugin.ts`), which
25
+ * belongs to the markdown pipeline that stays with the surface.
26
+ */
27
+ import { YStack } from '@hanzo/gui';
28
+ import type { ComponentProps, ReactNode } from 'react';
2
29
  export interface Source {
3
30
  id: string;
4
31
  /** Accessible name and tooltip — a title, repo or file path. */
5
32
  title: string;
6
33
  /** Opened on press. Surfaces without navigation can omit it. */
7
34
  href?: string;
8
- /** Favicon or mark. Falls back to the first letter of `title`. */
35
+ /** Favicon or mark. Falls back to the first letter of `host` or `title`. */
9
36
  icon?: ReactNode;
37
+ /**
38
+ * Who published it — `nytimes.com`, a repo, a filename. Shown above the
39
+ * title, because this is the line a reader actually scans.
40
+ *
41
+ * Derived by the SURFACE, not here. The extension parses it from the URL and
42
+ * strips `www.` (`Source.tsx:10-15`); chat is handed a domain by the server
43
+ * alongside a server-derived favicon. A `new URL()` in this package would be
44
+ * a third answer, and it would be the wrong one for a source that is a file
45
+ * path rather than a link.
46
+ */
47
+ host?: string;
10
48
  }
11
- export interface SourcesProps {
49
+ export interface SourcesProps extends Omit<ComponentProps<typeof YStack>, 'children'> {
12
50
  sources: Source[];
13
51
  title?: string;
14
52
  /** Raised on press. Given `href`, a surface may navigate instead. */
15
53
  onOpen?: (source: Source) => void;
16
54
  }
17
- export declare function Sources({ sources, title, onOpen }: SourcesProps): import("react/jsx-runtime").JSX.Element | null;
18
- export interface SourceChipProps {
55
+ export declare function Sources({ sources, title, onOpen, ...props }: SourcesProps): import("react/jsx-runtime").JSX.Element | null;
56
+ export interface SourceCardProps extends Omit<ComponentProps<typeof YStack>, 'children' | 'role'> {
19
57
  source: Source;
58
+ /** Position in the list, 1-based — the number the prose's marker refers to. */
59
+ index?: number;
20
60
  onOpen?: (source: Source) => void;
21
61
  }
22
- export declare function SourceChip({ source, onOpen }: SourceChipProps): import("react/jsx-runtime").JSX.Element;
62
+ export declare function SourceCard({ source, index, onOpen, ...props }: SourceCardProps): import("react/jsx-runtime").JSX.Element;
23
63
  //# sourceMappingURL=Sources.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"Sources.d.ts","sourceRoot":"","sources":["../../src/chat/Sources.tsx"],"names":[],"mappings":"AAWA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AAItC,MAAM,WAAW,MAAM;IACrB,EAAE,EAAE,MAAM,CAAA;IACV,gEAAgE;IAChE,KAAK,EAAE,MAAM,CAAA;IACb,gEAAgE;IAChE,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,kEAAkE;IAClE,IAAI,CAAC,EAAE,SAAS,CAAA;CACjB;AAED,MAAM,WAAW,YAAY;IAC3B,OAAO,EAAE,MAAM,EAAE,CAAA;IACjB,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,qEAAqE;IACrE,MAAM,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,IAAI,CAAA;CAClC;AAED,wBAAgB,OAAO,CAAC,EAAE,OAAO,EAAE,KAAiB,EAAE,MAAM,EAAE,EAAE,YAAY,kDAqB3E;AAED,MAAM,WAAW,eAAe;IAC9B,MAAM,EAAE,MAAM,CAAA;IACd,MAAM,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,IAAI,CAAA;CAClC;AAED,wBAAgB,UAAU,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,EAAE,eAAe,2CA+B7D"}
1
+ {"version":3,"file":"Sources.d.ts","sourceRoot":"","sources":["../../src/chat/Sources.tsx"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,OAAO,EAAuB,MAAM,EAAE,MAAM,YAAY,CAAA;AACxD,OAAO,KAAK,EAAE,cAAc,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AAStD,MAAM,WAAW,MAAM;IACrB,EAAE,EAAE,MAAM,CAAA;IACV,gEAAgE;IAChE,KAAK,EAAE,MAAM,CAAA;IACb,gEAAgE;IAChE,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,4EAA4E;IAC5E,IAAI,CAAC,EAAE,SAAS,CAAA;IAChB;;;;;;;;;OASG;IACH,IAAI,CAAC,EAAE,MAAM,CAAA;CACd;AAED,MAAM,WAAW,YAAa,SAAQ,IAAI,CAAC,cAAc,CAAC,OAAO,MAAM,CAAC,EAAE,UAAU,CAAC;IACnF,OAAO,EAAE,MAAM,EAAE,CAAA;IACjB,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,qEAAqE;IACrE,MAAM,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,IAAI,CAAA;CAClC;AAED,wBAAgB,OAAO,CAAC,EAAE,OAAO,EAAE,KAAiB,EAAE,MAAM,EAAE,GAAG,KAAK,EAAE,EAAE,YAAY,kDAiCrF;AAED,MAAM,WAAW,eAAgB,SAAQ,IAAI,CAAC,cAAc,CAAC,OAAO,MAAM,CAAC,EAAE,UAAU,GAAG,MAAM,CAAC;IAC/F,MAAM,EAAE,MAAM,CAAA;IACd,+EAA+E;IAC/E,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,MAAM,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,IAAI,CAAA;CAClC;AAED,wBAAgB,UAAU,CAAC,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,KAAK,EAAE,EAAE,eAAe,2CA6D9E"}
@@ -1,22 +1,45 @@
1
1
  'use client';
2
2
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
3
  /**
4
- * Sources — the citation grid for a turn.
4
+ * Sources — the citations for a turn, as a strip of cards.
5
5
  *
6
- * A dense grid of marks rather than a list of titles. Sources are scanned for
7
- * "which of these do I already trust", and twenty full-width titles would push
8
- * the rail well past a screen. Each mark keeps its title as accessible name and
9
- * tooltip, so the information is reordered, not discarded.
6
+ * This used to be a wrapping grid of 28px marks with the title reachable only
7
+ * as a tooltip, and the argument for it was density: sources are scanned for
8
+ * "which of these do I already trust", and twenty full-width titles push the
9
+ * rail past a screen. The density argument is right. Hiding the HOST was not,
10
+ * because the host is the answer to that exact question — a favicon is sixteen
11
+ * pixels of ambiguity and `nytimes.com` is a verdict — and neither surface that
12
+ * ships citations hides it. hanzo/chat's `SourceItem` (`components/Web/
13
+ * Sources.tsx`) draws favicon, domain, title and a snippet; the extension's
14
+ * `answer/Source.tsx:9-26` draws a number, a favicon, the host and a
15
+ * three-line-clamped title, and its own comment records where that came from:
16
+ * "Source card — chat AnswerView: 160px, number pill first, favicon, host,
17
+ * 3-line title".
18
+ *
19
+ * So density is kept by scrolling sideways rather than by discarding the text —
20
+ * the strip is one row tall whatever the count, which is what the grid was
21
+ * really buying. Both surfaces already scroll: `.ae-sources` is
22
+ * `display:flex; overflow-x:auto` at `newtab.css:260`.
23
+ *
24
+ * The number is not decoration. Both surfaces number the cards to match a
25
+ * marker in the prose, and the marker itself is not here: chat mints it with a
26
+ * remark plugin over private-use codepoints (`components/Web/plugin.ts`), which
27
+ * belongs to the markdown pipeline that stays with the surface.
10
28
  */
11
29
  import { SizableText, XStack, YStack } from '@hanzo/gui';
12
30
  import { slot, tip } from '../backends/gui/slot.js';
13
- export function Sources({ sources, title = 'Sources', onOpen }) {
31
+ /** Card width, px. The extension's is 168 and it cites chat's 160; the point of
32
+ * a fixed width is that a strip of them scans as a row rather than as ragged
33
+ * columns, so it is one number and not a range. */
34
+ const CARD = 168;
35
+ export function Sources({ sources, title = 'Sources', onOpen, ...props }) {
14
36
  if (sources.length === 0)
15
37
  return null;
16
- return (_jsxs(YStack, { ...slot('sources'), gap: "$2", p: "$3", rounded: "$4", borderWidth: 1, borderColor: "$borderColor", children: [_jsxs(XStack, { items: "center", gap: "$1.5", children: [_jsx(SizableText, { size: "$1", fontWeight: "500", color: "$color10", children: title }), _jsx(SizableText, { size: "$1", color: "$color9", children: sources.length })] }), _jsx(XStack, { flexWrap: "wrap", gap: "$1.5", children: sources.map((source) => (_jsx(SourceChip, { source: source, onOpen: onOpen }, source.id))) })] }));
38
+ return (_jsxs(YStack, { ...slot('sources'), gap: "$2", ...props, children: [_jsxs(XStack, { items: "center", gap: "$1.5", children: [_jsx(SizableText, { size: "$1", fontWeight: "500", color: "$color10", children: title }), _jsx(SizableText, { size: "$1", color: "$color9", children: sources.length })] }), _jsx(XStack, { ...slot('sources-strip'), overflow: "scroll", contain: "content", gap: "$2", pb: "$1", children: sources.map((source, i) => (_jsx(SourceCard, { source: source, index: i + 1, onOpen: onOpen }, source.id))) })] }));
17
39
  }
18
- export function SourceChip({ source, onOpen }) {
40
+ export function SourceCard({ source, index, onOpen, ...props }) {
19
41
  const press = onOpen ? () => onOpen(source) : undefined;
20
- return (_jsx(XStack, { ...slot('source-chip'), width: 28, height: 28, items: "center", justify: "center", rounded: "$3", borderWidth: 1, borderColor: "$borderColor", bg: "$color3", overflow: "hidden", cursor: press ? 'pointer' : undefined, onPress: press, role: press ? 'button' : undefined, tabIndex: press ? 0 : undefined, "aria-label": source.title, ...tip(source.title), hoverStyle: press ? { borderColor: '$color8' } : undefined, pressStyle: press ? { bg: '$color5' } : undefined, children: source.icon ?? (_jsx(SizableText, { size: "$1", color: "$color11", children: source.title.charAt(0).toUpperCase() })) }));
42
+ const label = source.host ?? source.title;
43
+ return (_jsxs(YStack, { ...slot('source-card'), width: CARD, shrink: 0, gap: "$1.5", p: "$2.5", rounded: "$4", borderWidth: 1, borderColor: "$borderColor", bg: "$color2", cursor: press ? 'pointer' : undefined, onPress: press, role: press ? 'button' : undefined, tabIndex: press ? 0 : undefined, "aria-label": source.title, ...tip(source.title), hoverStyle: press ? { borderColor: '$color8' } : undefined, pressStyle: press ? { bg: '$color4' } : undefined, ...props, children: [_jsxs(XStack, { items: "center", gap: "$1.5", children: [index != null ? (_jsx(SizableText, { size: "$1", color: "$color9", children: index })) : null, _jsx(XStack, { width: 14, height: 14, shrink: 0, items: "center", justify: "center", rounded: "$1", overflow: "hidden", bg: "$color4", children: source.icon ?? (_jsx(SizableText, { size: "$1", color: "$color11", children: label.charAt(0).toUpperCase() })) }), _jsx(SizableText, { size: "$1", color: "$color10", numberOfLines: 1, children: label })] }), _jsx(SizableText, { size: "$1", color: "$color12", numberOfLines: 3, children: source.title })] }));
21
44
  }
22
45
  //# sourceMappingURL=Sources.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"Sources.js","sourceRoot":"","sources":["../../src/chat/Sources.tsx"],"names":[],"mappings":"AAAA,YAAY,CAAA;;AAEZ;;;;;;;GAOG;AACH,OAAO,EAAE,WAAW,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,YAAY,CAAA;AAGxD,OAAO,EAAE,IAAI,EAAE,GAAG,EAAE,MAAM,sBAAsB,CAAA;AAmBhD,MAAM,UAAU,OAAO,CAAC,EAAE,OAAO,EAAE,KAAK,GAAG,SAAS,EAAE,MAAM,EAAgB;IAC1E,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,IAAI,CAAA;IAErC,OAAO,CACL,MAAC,MAAM,OAAK,IAAI,CAAC,SAAS,CAAC,EAAE,GAAG,EAAC,IAAI,EAAC,CAAC,EAAC,IAAI,EAAC,OAAO,EAAC,IAAI,EAAC,WAAW,EAAE,CAAC,EAAE,WAAW,EAAC,cAAc,aAClG,MAAC,MAAM,IAAC,KAAK,EAAC,QAAQ,EAAC,GAAG,EAAC,MAAM,aAC/B,KAAC,WAAW,IAAC,IAAI,EAAC,IAAI,EAAC,UAAU,EAAC,KAAK,EAAC,KAAK,EAAC,UAAU,YACrD,KAAK,GACM,EACd,KAAC,WAAW,IAAC,IAAI,EAAC,IAAI,EAAC,KAAK,EAAC,SAAS,YACnC,OAAO,CAAC,MAAM,GACH,IACP,EAET,KAAC,MAAM,IAAC,QAAQ,EAAC,MAAM,EAAC,GAAG,EAAC,MAAM,YAC/B,OAAO,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,CACvB,KAAC,UAAU,IAAiB,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,IAAzC,MAAM,CAAC,EAAE,CAAoC,CAC/D,CAAC,GACK,IACF,CACV,CAAA;AACH,CAAC;AAOD,MAAM,UAAU,UAAU,CAAC,EAAE,MAAM,EAAE,MAAM,EAAmB;IAC5D,MAAM,KAAK,GAAG,MAAM,CAAC,CAAC,CAAC,GAAG,EAAE,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,SAAS,CAAA;IAEvD,OAAO,CACL,KAAC,MAAM,OACD,IAAI,CAAC,aAAa,CAAC,EACvB,KAAK,EAAE,EAAE,EACT,MAAM,EAAE,EAAE,EACV,KAAK,EAAC,QAAQ,EACd,OAAO,EAAC,QAAQ,EAChB,OAAO,EAAC,IAAI,EACZ,WAAW,EAAE,CAAC,EACd,WAAW,EAAC,cAAc,EAC1B,EAAE,EAAC,SAAS,EACZ,QAAQ,EAAC,QAAQ,EACjB,MAAM,EAAE,KAAK,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,SAAS,EACrC,OAAO,EAAE,KAAK,EACd,IAAI,EAAE,KAAK,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,SAAS,EAClC,QAAQ,EAAE,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS,gBACnB,MAAM,CAAC,KAAK,KACpB,GAAG,CAAC,MAAM,CAAC,KAAK,CAAC,EACrB,UAAU,EAAE,KAAK,CAAC,CAAC,CAAC,EAAE,WAAW,EAAE,SAAS,EAAE,CAAC,CAAC,CAAC,SAAS,EAC1D,UAAU,EAAE,KAAK,CAAC,CAAC,CAAC,EAAE,EAAE,EAAE,SAAS,EAAE,CAAC,CAAC,CAAC,SAAS,YAEhD,MAAM,CAAC,IAAI,IAAI,CACd,KAAC,WAAW,IAAC,IAAI,EAAC,IAAI,EAAC,KAAK,EAAC,UAAU,YACpC,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,WAAW,EAAE,GACzB,CACf,GACM,CACV,CAAA;AACH,CAAC"}
1
+ {"version":3,"file":"Sources.js","sourceRoot":"","sources":["../../src/chat/Sources.tsx"],"names":[],"mappings":"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"}
@@ -65,7 +65,7 @@ const mark = (status) => {
65
65
  return (0, jsx_runtime_1.jsx)(lucide_icons_2_1.Ban, { size: MARK, color: "$color11" });
66
66
  }
67
67
  };
68
- function Step({ name, status = 'done', detail, children, defaultOpen, open, onOpenChange, }) {
68
+ function Step({ name, status = 'done', detail, children, defaultOpen, open, onOpenChange, ...props }) {
69
69
  // A step with no body is a statement, not a control. Rendering a dead chevron
70
70
  // and a focusable trigger over nothing is how a keyboard user is sent to a
71
71
  // stop that does not answer.
@@ -77,5 +77,5 @@ function Step({ name, status = 'done', detail, children, defaultOpen, open, onOp
77
77
  // its `stopped` register with a border rather than a fill. A step that
78
78
  // errored is set apart the same way, so it still reads as a failure in a
79
79
  // brand that retunes the whole ramp.
80
- borderColor: status === 'error' ? '$color9' : '$borderColor', bg: "$color2", overflow: "hidden", children: [opens ? (0, jsx_runtime_1.jsx)(gui_2.CollapsibleTrigger, { width: "100%", children: header }) : header, opens ? ((0, jsx_runtime_1.jsx)(gui_2.CollapsibleContent, { children: (0, jsx_runtime_1.jsx)(gui_1.YStack, { ...(0, slot_1.slot)('step-body'), px: "$3", py: "$2.5", gap: "$2", borderTopWidth: 1, borderColor: "$borderColor", children: children }) })) : null] }));
80
+ borderColor: status === 'error' ? '$color9' : '$borderColor', bg: "$color2", overflow: "hidden", ...props, children: [opens ? (0, jsx_runtime_1.jsx)(gui_2.CollapsibleTrigger, { width: "100%", children: header }) : header, opens ? ((0, jsx_runtime_1.jsx)(gui_2.CollapsibleContent, { children: (0, jsx_runtime_1.jsx)(gui_1.YStack, { ...(0, slot_1.slot)('step-body'), px: "$3", py: "$2.5", gap: "$2", borderTopWidth: 1, borderColor: "$borderColor", children: children }) })) : null] }));
81
81
  }
@@ -1,4 +1,5 @@
1
- import type { ReactNode } from 'react';
1
+ import type { ComponentProps, ReactNode } from 'react';
2
+ import { Collapsible } from '../backends/gui/index.js';
2
3
  /**
3
4
  * How a step went.
4
5
  *
@@ -10,7 +11,7 @@ import type { ReactNode } from 'react';
10
11
  * step that is running are both "not yet".
11
12
  */
12
13
  export type Ran = 'running' | 'done' | 'error' | 'cancelled';
13
- export interface StepProps {
14
+ export interface StepProps extends Omit<ComponentProps<typeof Collapsible>, 'children' | 'open' | 'defaultOpen' | 'onOpenChange'> {
14
15
  /** What ran — a tool name, "Reasoning", "Plan". */
15
16
  name: string;
16
17
  status?: Ran;
@@ -30,5 +31,5 @@ export interface StepProps {
30
31
  open?: boolean;
31
32
  onOpenChange?: (open: boolean) => void;
32
33
  }
33
- export declare function Step({ name, status, detail, children, defaultOpen, open, onOpenChange, }: StepProps): import("react/jsx-runtime").JSX.Element;
34
+ export declare function Step({ name, status, detail, children, defaultOpen, open, onOpenChange, ...props }: StepProps): import("react/jsx-runtime").JSX.Element;
34
35
  //# sourceMappingURL=Step.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"Step.d.ts","sourceRoot":"","sources":["../../src/chat/Step.tsx"],"names":[],"mappings":"AAoCA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AAKtC;;;;;;;;;GASG;AACH,MAAM,MAAM,GAAG,GAAG,SAAS,GAAG,MAAM,GAAG,OAAO,GAAG,WAAW,CAAA;AA8B5D,MAAM,WAAW,SAAS;IACxB,mDAAmD;IACnD,IAAI,EAAE,MAAM,CAAA;IACZ,MAAM,CAAC,EAAE,GAAG,CAAA;IACZ;;;;;OAKG;IACH,MAAM,CAAC,EAAE,MAAM,CAAA;IACf;kEAC8D;IAC9D,QAAQ,CAAC,EAAE,SAAS,CAAA;IACpB,oBAAoB;IACpB,WAAW,CAAC,EAAE,OAAO,CAAA;IACrB,+EAA+E;IAC/E,IAAI,CAAC,EAAE,OAAO,CAAA;IACd,YAAY,CAAC,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI,CAAA;CACvC;AAED,wBAAgB,IAAI,CAAC,EACnB,IAAI,EACJ,MAAe,EACf,MAAM,EACN,QAAQ,EACR,WAAW,EACX,IAAI,EACJ,YAAY,GACb,EAAE,SAAS,2CA4DX"}
1
+ {"version":3,"file":"Step.d.ts","sourceRoot":"","sources":["../../src/chat/Step.tsx"],"names":[],"mappings":"AAoCA,OAAO,KAAK,EAAE,cAAc,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AAEtD,OAAO,EAAE,WAAW,EAAmD,MAAM,iBAAiB,CAAA;AAG9F;;;;;;;;;GASG;AACH,MAAM,MAAM,GAAG,GAAG,SAAS,GAAG,MAAM,GAAG,OAAO,GAAG,WAAW,CAAA;AA8B5D,MAAM,WAAW,SACf,SAAQ,IAAI,CAAC,cAAc,CAAC,OAAO,WAAW,CAAC,EAAE,UAAU,GAAG,MAAM,GAAG,aAAa,GAAG,cAAc,CAAC;IACtG,mDAAmD;IACnD,IAAI,EAAE,MAAM,CAAA;IACZ,MAAM,CAAC,EAAE,GAAG,CAAA;IACZ;;;;;OAKG;IACH,MAAM,CAAC,EAAE,MAAM,CAAA;IACf;kEAC8D;IAC9D,QAAQ,CAAC,EAAE,SAAS,CAAA;IACpB,oBAAoB;IACpB,WAAW,CAAC,EAAE,OAAO,CAAA;IACrB,+EAA+E;IAC/E,IAAI,CAAC,EAAE,OAAO,CAAA;IACd,YAAY,CAAC,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI,CAAA;CACvC;AAED,wBAAgB,IAAI,CAAC,EACnB,IAAI,EACJ,MAAe,EACf,MAAM,EACN,QAAQ,EACR,WAAW,EACX,IAAI,EACJ,YAAY,EACZ,GAAG,KAAK,EACT,EAAE,SAAS,2CA6DX"}
package/dist/chat/Step.js CHANGED
@@ -62,7 +62,7 @@ const mark = (status) => {
62
62
  return _jsx(Ban, { size: MARK, color: "$color11" });
63
63
  }
64
64
  };
65
- export function Step({ name, status = 'done', detail, children, defaultOpen, open, onOpenChange, }) {
65
+ export function Step({ name, status = 'done', detail, children, defaultOpen, open, onOpenChange, ...props }) {
66
66
  // A step with no body is a statement, not a control. Rendering a dead chevron
67
67
  // and a focusable trigger over nothing is how a keyboard user is sent to a
68
68
  // stop that does not answer.
@@ -74,6 +74,6 @@ export function Step({ name, status = 'done', detail, children, defaultOpen, ope
74
74
  // its `stopped` register with a border rather than a fill. A step that
75
75
  // errored is set apart the same way, so it still reads as a failure in a
76
76
  // brand that retunes the whole ramp.
77
- borderColor: status === 'error' ? '$color9' : '$borderColor', bg: "$color2", overflow: "hidden", children: [opens ? _jsx(CollapsibleTrigger, { width: "100%", children: header }) : header, opens ? (_jsx(CollapsibleContent, { children: _jsx(YStack, { ...slot('step-body'), px: "$3", py: "$2.5", gap: "$2", borderTopWidth: 1, borderColor: "$borderColor", children: children }) })) : null] }));
77
+ borderColor: status === 'error' ? '$color9' : '$borderColor', bg: "$color2", overflow: "hidden", ...props, children: [opens ? _jsx(CollapsibleTrigger, { width: "100%", children: header }) : header, opens ? (_jsx(CollapsibleContent, { children: _jsx(YStack, { ...slot('step-body'), px: "$3", py: "$2.5", gap: "$2", borderTopWidth: 1, borderColor: "$borderColor", children: children }) })) : null] }));
78
78
  }
79
79
  //# sourceMappingURL=Step.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"Step.js","sourceRoot":"","sources":["../../src/chat/Step.tsx"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"Step.js","sourceRoot":"","sources":["../../src/chat/Step.tsx"],"names":[],"mappings":"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"}
@@ -1,7 +1,7 @@
1
1
  'use client';"use strict";
2
2
  'use client';
3
3
  Object.defineProperty(exports, "__esModule", { value: true });
4
- exports.Thread = Thread;
4
+ exports.Thread = void 0;
5
5
  const jsx_runtime_1 = require("react/jsx-runtime");
6
6
  /**
7
7
  * Thread — the scrolling column of turns.
@@ -10,6 +10,28 @@ const jsx_runtime_1 = require("react/jsx-runtime");
10
10
  * to the bottom, and stop following the instant the reader scrolls up. The
11
11
  * decision is `pinned()`, asserted without a DOM; this component only wires it
12
12
  * to the platform's scroll view, so momentum and keyboard stay the host's.
13
+ *
14
+ * That decision is now the fleet's only answer to "am I at the bottom", which
15
+ * had five: 50px in two places, 97%-of-height in a third, an
16
+ * `IntersectionObserver` at a 0.85 ratio in a fourth, and on the thread itself
17
+ * nothing at all — hanzo.app `/chat` ran `scrollIntoView({behavior:'smooth'})`
18
+ * on every `messages` change, and since a streaming turn rewrites `messages`
19
+ * per SSE delta, scrolling up to re-read was undone several times a second.
20
+ *
21
+ * Measured in Chromium against the packed tarball, 2500 tokens at 4ms:
22
+ * following holds a 0px gap through 2501 commits; parked at offset 0 the reader
23
+ * STAYS at 0 while content grows 1015 → 1375; returning to the end resumes
24
+ * following at 0px.
25
+ *
26
+ * There is no `onLayout` here any more, and nothing is lost with it. It was
27
+ * passed to keep a `track` ref current, gui does not implement it on this
28
+ * component so React forwarded it to the DOM as an unknown handler — the noise
29
+ * hanzo.app reported — and the ref it fed was WRITE-ONLY: `track` was read in
30
+ * exactly one place, `onScroll`, which rebuilds all three numbers from the
31
+ * event before reading them. So the handler was dead, the state it kept was
32
+ * dead, and the measurement the two together looked like they were doing was
33
+ * being done properly a few lines below the whole time. The scroll event
34
+ * carries `layoutMeasurement`; nothing else needs to ask.
13
35
  */
14
36
  const gui_1 = require("@hanzo/gui");
15
37
  const react_1 = require("react");
@@ -17,26 +39,29 @@ const slot_1 = require("../backends/gui/slot.cjs");
17
39
  const stick_1 = require("./stick.cjs");
18
40
  /** Reading-measure cap for prose, in px. */
19
41
  const MEASURE = 768;
20
- function Thread({ children, maxWidth = MEASURE, slack = stick_1.SLACK, gap = 24 }) {
42
+ exports.Thread = (0, react_1.forwardRef)(function Thread({ children, maxWidth = MEASURE, slack = stick_1.SLACK, gap = 24, onScroll, column, ...props }, ref) {
21
43
  const view = (0, react_1.useRef)(null);
22
- const track = (0, react_1.useRef)({ offset: 0, viewport: 0, content: 0 });
23
44
  const follow = (0, react_1.useRef)(true);
45
+ // Ours to scroll with, the caller's because three consumers need this node.
46
+ const hold = (0, react_1.useCallback)((el) => {
47
+ view.current = el;
48
+ if (typeof ref === 'function')
49
+ ref(el);
50
+ else if (ref)
51
+ ref.current = el;
52
+ }, [ref]);
24
53
  const grew = (0, react_1.useCallback)(() => {
25
54
  if (follow.current)
26
55
  view.current?.scrollToEnd({ animated: false });
27
56
  }, []);
28
- return ((0, jsx_runtime_1.jsx)(gui_1.ScrollView, { ref: view, ...(0, slot_1.slot)('thread'), flex: 1, width: "100%", scrollEventThrottle: 16, onLayout: (e) => {
29
- track.current = { ...track.current, viewport: e.nativeEvent.layout.height };
30
- }, onContentSizeChange: (_w, h) => {
31
- track.current = { ...track.current, content: h };
32
- grew();
33
- }, onScroll: (e) => {
57
+ return ((0, jsx_runtime_1.jsx)(gui_1.ScrollView, { ref: hold, ...(0, slot_1.slot)('thread'), flex: 1, width: "100%", scrollEventThrottle: 16, onContentSizeChange: grew, onScroll: (e) => {
34
58
  const { contentOffset, layoutMeasurement, contentSize } = e.nativeEvent;
35
- track.current = {
59
+ const track = {
36
60
  offset: contentOffset.y,
37
61
  viewport: layoutMeasurement.height,
38
62
  content: contentSize.height,
39
63
  };
40
- follow.current = (0, stick_1.pinned)(track.current, slack);
41
- }, children: (0, jsx_runtime_1.jsx)(gui_1.YStack, { ...(0, slot_1.slot)('thread-column'), width: "100%", gap: gap, py: gap, ...(maxWidth ? { maxW: maxWidth, self: 'center', px: '$3' } : null), children: children }) }));
42
- }
64
+ follow.current = (0, stick_1.pinned)(track, slack);
65
+ onScroll?.(e, { pinned: follow.current, track });
66
+ }, ...props, children: (0, jsx_runtime_1.jsx)(gui_1.YStack, { ...(0, slot_1.slot)('thread-column'), width: "100%", gap: gap, py: gap, ...(maxWidth ? { maxW: maxWidth, self: 'center', px: '$3' } : null), ...column, children: children }) }));
67
+ });
@@ -1,11 +1,63 @@
1
- import { type ReactNode } from 'react';
2
- export interface ThreadProps {
1
+ /**
2
+ * Thread — the scrolling column of turns.
3
+ *
4
+ * It does the one thing every surface had re-solved: follow a streaming answer
5
+ * to the bottom, and stop following the instant the reader scrolls up. The
6
+ * decision is `pinned()`, asserted without a DOM; this component only wires it
7
+ * to the platform's scroll view, so momentum and keyboard stay the host's.
8
+ *
9
+ * That decision is now the fleet's only answer to "am I at the bottom", which
10
+ * had five: 50px in two places, 97%-of-height in a third, an
11
+ * `IntersectionObserver` at a 0.85 ratio in a fourth, and on the thread itself
12
+ * nothing at all — hanzo.app `/chat` ran `scrollIntoView({behavior:'smooth'})`
13
+ * on every `messages` change, and since a streaming turn rewrites `messages`
14
+ * per SSE delta, scrolling up to re-read was undone several times a second.
15
+ *
16
+ * Measured in Chromium against the packed tarball, 2500 tokens at 4ms:
17
+ * following holds a 0px gap through 2501 commits; parked at offset 0 the reader
18
+ * STAYS at 0 while content grows 1015 → 1375; returning to the end resumes
19
+ * following at 0px.
20
+ *
21
+ * There is no `onLayout` here any more, and nothing is lost with it. It was
22
+ * passed to keep a `track` ref current, gui does not implement it on this
23
+ * component so React forwarded it to the DOM as an unknown handler — the noise
24
+ * hanzo.app reported — and the ref it fed was WRITE-ONLY: `track` was read in
25
+ * exactly one place, `onScroll`, which rebuilds all three numbers from the
26
+ * event before reading them. So the handler was dead, the state it kept was
27
+ * dead, and the measurement the two together looked like they were doing was
28
+ * being done properly a few lines below the whole time. The scroll event
29
+ * carries `layoutMeasurement`; nothing else needs to ask.
30
+ */
31
+ import { ScrollView, YStack, type GetRef } from '@hanzo/gui';
32
+ import { type ComponentProps, type ReactNode } from 'react';
33
+ import { type Track } from './stick.js';
34
+ type Scroller = GetRef<typeof ScrollView> & {
35
+ scrollToEnd(o: {
36
+ animated: boolean;
37
+ }): void;
38
+ };
39
+ export interface ThreadProps extends Omit<ComponentProps<typeof ScrollView>, 'children' | 'onScroll'> {
3
40
  children?: ReactNode;
4
41
  /** Reading-measure cap. Pass `0` to fill the container. */
5
42
  maxWidth?: number;
6
43
  /** How near the end still counts as "at the bottom", in px. */
7
44
  slack?: number;
8
45
  gap?: number;
46
+ /**
47
+ * The scroll, after the follow decision is made — with `at`, the answer.
48
+ *
49
+ * Three separate consumers need this element and this fact: the follow
50
+ * decision, a scroll-to-bottom button, and a surface's own turn-offset
51
+ * bookkeeping. Deciding privately and publishing nothing is what left each of
52
+ * them measuring the scroller again, differently.
53
+ */
54
+ onScroll?: (event: unknown, at: {
55
+ pinned: boolean;
56
+ track: Track;
57
+ }) => void;
58
+ /** The column that holds the turns, for a surface that needs to reach it. */
59
+ column?: Omit<ComponentProps<typeof YStack>, 'children'>;
9
60
  }
10
- export declare function Thread({ children, maxWidth, slack, gap }: ThreadProps): import("react/jsx-runtime").JSX.Element;
61
+ export declare const Thread: import("react").ForwardRefExoticComponent<Omit<ThreadProps, "ref"> & import("react").RefAttributes<Scroller>>;
62
+ export {};
11
63
  //# sourceMappingURL=Thread.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"Thread.d.ts","sourceRoot":"","sources":["../../src/chat/Thread.tsx"],"names":[],"mappings":"AAWA,OAAO,EAAuB,KAAK,SAAS,EAAE,MAAM,OAAO,CAAA;AAQ3D,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,EAAE,SAAS,CAAA;IACpB,2DAA2D;IAC3D,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,+DAA+D;IAC/D,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,GAAG,CAAC,EAAE,MAAM,CAAA;CACb;AAED,wBAAgB,MAAM,CAAC,EAAE,QAAQ,EAAE,QAAkB,EAAE,KAAa,EAAE,GAAQ,EAAE,EAAE,WAAW,2CA+C5F"}
1
+ {"version":3,"file":"Thread.d.ts","sourceRoot":"","sources":["../../src/chat/Thread.tsx"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,OAAO,EAAE,UAAU,EAAE,MAAM,EAAE,KAAK,MAAM,EAAE,MAAM,YAAY,CAAA;AAC5D,OAAO,EAAmC,KAAK,cAAc,EAAE,KAAK,SAAS,EAAE,MAAM,OAAO,CAAA;AAG5F,OAAO,EAAiB,KAAK,KAAK,EAAE,MAAM,SAAS,CAAA;AAKnD,KAAK,QAAQ,GAAG,MAAM,CAAC,OAAO,UAAU,CAAC,GAAG;IAAE,WAAW,CAAC,CAAC,EAAE;QAAE,QAAQ,EAAE,OAAO,CAAA;KAAE,GAAG,IAAI,CAAA;CAAE,CAAA;AAE3F,MAAM,WAAW,WACf,SAAQ,IAAI,CAAC,cAAc,CAAC,OAAO,UAAU,CAAC,EAAE,UAAU,GAAG,UAAU,CAAC;IACxE,QAAQ,CAAC,EAAE,SAAS,CAAA;IACpB,2DAA2D;IAC3D,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,+DAA+D;IAC/D,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ;;;;;;;OAOG;IACH,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,EAAE,EAAE,EAAE;QAAE,MAAM,EAAE,OAAO,CAAC;QAAC,KAAK,EAAE,KAAK,CAAA;KAAE,KAAK,IAAI,CAAA;IAC1E,6EAA6E;IAC7E,MAAM,CAAC,EAAE,IAAI,CAAC,cAAc,CAAC,OAAO,MAAM,CAAC,EAAE,UAAU,CAAC,CAAA;CACzD;AAED,eAAO,MAAM,MAAM,+GAqDjB,CAAA"}
@@ -7,34 +7,59 @@ import { jsx as _jsx } from "react/jsx-runtime";
7
7
  * to the bottom, and stop following the instant the reader scrolls up. The
8
8
  * decision is `pinned()`, asserted without a DOM; this component only wires it
9
9
  * to the platform's scroll view, so momentum and keyboard stay the host's.
10
+ *
11
+ * That decision is now the fleet's only answer to "am I at the bottom", which
12
+ * had five: 50px in two places, 97%-of-height in a third, an
13
+ * `IntersectionObserver` at a 0.85 ratio in a fourth, and on the thread itself
14
+ * nothing at all — hanzo.app `/chat` ran `scrollIntoView({behavior:'smooth'})`
15
+ * on every `messages` change, and since a streaming turn rewrites `messages`
16
+ * per SSE delta, scrolling up to re-read was undone several times a second.
17
+ *
18
+ * Measured in Chromium against the packed tarball, 2500 tokens at 4ms:
19
+ * following holds a 0px gap through 2501 commits; parked at offset 0 the reader
20
+ * STAYS at 0 while content grows 1015 → 1375; returning to the end resumes
21
+ * following at 0px.
22
+ *
23
+ * There is no `onLayout` here any more, and nothing is lost with it. It was
24
+ * passed to keep a `track` ref current, gui does not implement it on this
25
+ * component so React forwarded it to the DOM as an unknown handler — the noise
26
+ * hanzo.app reported — and the ref it fed was WRITE-ONLY: `track` was read in
27
+ * exactly one place, `onScroll`, which rebuilds all three numbers from the
28
+ * event before reading them. So the handler was dead, the state it kept was
29
+ * dead, and the measurement the two together looked like they were doing was
30
+ * being done properly a few lines below the whole time. The scroll event
31
+ * carries `layoutMeasurement`; nothing else needs to ask.
10
32
  */
11
33
  import { ScrollView, YStack } from '@hanzo/gui';
12
- import { useCallback, useRef } from 'react';
34
+ import { forwardRef, useCallback, useRef } from 'react';
13
35
  import { slot } from '../backends/gui/slot.js';
14
36
  import { pinned, SLACK } from './stick.js';
15
37
  /** Reading-measure cap for prose, in px. */
16
38
  const MEASURE = 768;
17
- export function Thread({ children, maxWidth = MEASURE, slack = SLACK, gap = 24 }) {
39
+ export const Thread = /* @__PURE__ */ forwardRef(function Thread({ children, maxWidth = MEASURE, slack = SLACK, gap = 24, onScroll, column, ...props }, ref) {
18
40
  const view = useRef(null);
19
- const track = useRef({ offset: 0, viewport: 0, content: 0 });
20
41
  const follow = useRef(true);
42
+ // Ours to scroll with, the caller's because three consumers need this node.
43
+ const hold = useCallback((el) => {
44
+ view.current = el;
45
+ if (typeof ref === 'function')
46
+ ref(el);
47
+ else if (ref)
48
+ ref.current = el;
49
+ }, [ref]);
21
50
  const grew = useCallback(() => {
22
51
  if (follow.current)
23
52
  view.current?.scrollToEnd({ animated: false });
24
53
  }, []);
25
- return (_jsx(ScrollView, { ref: view, ...slot('thread'), flex: 1, width: "100%", scrollEventThrottle: 16, onLayout: (e) => {
26
- track.current = { ...track.current, viewport: e.nativeEvent.layout.height };
27
- }, onContentSizeChange: (_w, h) => {
28
- track.current = { ...track.current, content: h };
29
- grew();
30
- }, onScroll: (e) => {
54
+ return (_jsx(ScrollView, { ref: hold, ...slot('thread'), flex: 1, width: "100%", scrollEventThrottle: 16, onContentSizeChange: grew, onScroll: (e) => {
31
55
  const { contentOffset, layoutMeasurement, contentSize } = e.nativeEvent;
32
- track.current = {
56
+ const track = {
33
57
  offset: contentOffset.y,
34
58
  viewport: layoutMeasurement.height,
35
59
  content: contentSize.height,
36
60
  };
37
- follow.current = pinned(track.current, slack);
38
- }, children: _jsx(YStack, { ...slot('thread-column'), width: "100%", gap: gap, py: gap, ...(maxWidth ? { maxW: maxWidth, self: 'center', px: '$3' } : null), children: children }) }));
39
- }
61
+ follow.current = pinned(track, slack);
62
+ onScroll?.(e, { pinned: follow.current, track });
63
+ }, ...props, children: _jsx(YStack, { ...slot('thread-column'), width: "100%", gap: gap, py: gap, ...(maxWidth ? { maxW: maxWidth, self: 'center', px: '$3' } : null), ...column, children: children }) }));
64
+ });
40
65
  //# sourceMappingURL=Thread.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"Thread.js","sourceRoot":"","sources":["../../src/chat/Thread.tsx"],"names":[],"mappings":"AAAA,YAAY,CAAA;;AAEZ;;;;;;;GAOG;AACH,OAAO,EAAE,UAAU,EAAE,MAAM,EAAe,MAAM,YAAY,CAAA;AAC5D,OAAO,EAAE,WAAW,EAAE,MAAM,EAAkB,MAAM,OAAO,CAAA;AAE3D,OAAO,EAAE,IAAI,EAAE,MAAM,sBAAsB,CAAA;AAC3C,OAAO,EAAE,MAAM,EAAE,KAAK,EAAc,MAAM,SAAS,CAAA;AAEnD,4CAA4C;AAC5C,MAAM,OAAO,GAAG,GAAG,CAAA;AAWnB,MAAM,UAAU,MAAM,CAAC,EAAE,QAAQ,EAAE,QAAQ,GAAG,OAAO,EAAE,KAAK,GAAG,KAAK,EAAE,GAAG,GAAG,EAAE,EAAe;IAC3F,MAAM,IAAI,GAAG,MAAM,CAGjB,IAAI,CAAC,CAAA;IACP,MAAM,KAAK,GAAG,MAAM,CAAQ,EAAE,MAAM,EAAE,CAAC,EAAE,QAAQ,EAAE,CAAC,EAAE,OAAO,EAAE,CAAC,EAAE,CAAC,CAAA;IACnE,MAAM,MAAM,GAAG,MAAM,CAAC,IAAI,CAAC,CAAA;IAE3B,MAAM,IAAI,GAAG,WAAW,CAAC,GAAG,EAAE;QAC5B,IAAI,MAAM,CAAC,OAAO;YAAE,IAAI,CAAC,OAAO,EAAE,WAAW,CAAC,EAAE,QAAQ,EAAE,KAAK,EAAE,CAAC,CAAA;IACpE,CAAC,EAAE,EAAE,CAAC,CAAA;IAEN,OAAO,CACL,KAAC,UAAU,IACT,GAAG,EAAE,IAAI,KACL,IAAI,CAAC,QAAQ,CAAC,EAClB,IAAI,EAAE,CAAC,EACP,KAAK,EAAC,MAAM,EACZ,mBAAmB,EAAE,EAAE,EACvB,QAAQ,EAAE,CAAC,CAAM,EAAE,EAAE;YACnB,KAAK,CAAC,OAAO,GAAG,EAAE,GAAG,KAAK,CAAC,OAAO,EAAE,QAAQ,EAAE,CAAC,CAAC,WAAW,CAAC,MAAM,CAAC,MAAM,EAAE,CAAA;QAC7E,CAAC,EACD,mBAAmB,EAAE,CAAC,EAAO,EAAE,CAAM,EAAE,EAAE;YACvC,KAAK,CAAC,OAAO,GAAG,EAAE,GAAG,KAAK,CAAC,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE,CAAA;YAChD,IAAI,EAAE,CAAA;QACR,CAAC,EACD,QAAQ,EAAE,CAAC,CAAM,EAAE,EAAE;YACnB,MAAM,EAAE,aAAa,EAAE,iBAAiB,EAAE,WAAW,EAAE,GAAG,CAAC,CAAC,WAAW,CAAA;YACvE,KAAK,CAAC,OAAO,GAAG;gBACd,MAAM,EAAE,aAAa,CAAC,CAAC;gBACvB,QAAQ,EAAE,iBAAiB,CAAC,MAAM;gBAClC,OAAO,EAAE,WAAW,CAAC,MAAM;aAC5B,CAAA;YACD,MAAM,CAAC,OAAO,GAAG,MAAM,CAAC,KAAK,CAAC,OAAO,EAAE,KAAK,CAAC,CAAA;QAC/C,CAAC,YAED,KAAC,MAAM,OACD,IAAI,CAAC,eAAe,CAAC,EACzB,KAAK,EAAC,MAAM,EACZ,GAAG,EAAE,GAAG,EACR,EAAE,EAAE,GAAG,KACH,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,QAAiB,EAAE,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,YAE5E,QAAQ,GACF,GACE,CACd,CAAA;AACH,CAAC"}
1
+ {"version":3,"file":"Thread.js","sourceRoot":"","sources":["../../src/chat/Thread.tsx"],"names":[],"mappings":"AAAA,YAAY,CAAA;;AAEZ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,OAAO,EAAE,UAAU,EAAE,MAAM,EAAe,MAAM,YAAY,CAAA;AAC5D,OAAO,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,EAAuC,MAAM,OAAO,CAAA;AAE5F,OAAO,EAAE,IAAI,EAAE,MAAM,sBAAsB,CAAA;AAC3C,OAAO,EAAE,MAAM,EAAE,KAAK,EAAc,MAAM,SAAS,CAAA;AAEnD,4CAA4C;AAC5C,MAAM,OAAO,GAAG,GAAG,CAAA;AAyBnB,MAAM,CAAC,MAAM,MAAM,GAAG,eAAe,CAAC,UAAU,CAAwB,SAAS,MAAM,CACrF,EAAE,QAAQ,EAAE,QAAQ,GAAG,OAAO,EAAE,KAAK,GAAG,KAAK,EAAE,GAAG,GAAG,EAAE,EAAE,QAAQ,EAAE,MAAM,EAAE,GAAG,KAAK,EAAE,EACrF,GAAG;IAEH,MAAM,IAAI,GAAG,MAAM,CAAkB,IAAI,CAAC,CAAA;IAC1C,MAAM,MAAM,GAAG,MAAM,CAAC,IAAI,CAAC,CAAA;IAE3B,4EAA4E;IAC5E,MAAM,IAAI,GAAG,WAAW,CACtB,CAAC,EAAmB,EAAE,EAAE;QACtB,IAAI,CAAC,OAAO,GAAG,EAAE,CAAA;QACjB,IAAI,OAAO,GAAG,KAAK,UAAU;YAAE,GAAG,CAAC,EAAE,CAAC,CAAA;aACjC,IAAI,GAAG;YAAG,GAA+C,CAAC,OAAO,GAAG,EAAE,CAAA;IAC7E,CAAC,EACD,CAAC,GAAG,CAAC,CACN,CAAA;IAED,MAAM,IAAI,GAAG,WAAW,CAAC,GAAG,EAAE;QAC5B,IAAI,MAAM,CAAC,OAAO;YAAE,IAAI,CAAC,OAAO,EAAE,WAAW,CAAC,EAAE,QAAQ,EAAE,KAAK,EAAE,CAAC,CAAA;IACpE,CAAC,EAAE,EAAE,CAAC,CAAA;IAEN,OAAO,CACL,KAAC,UAAU,IACT,GAAG,EAAE,IAAa,KACd,IAAI,CAAC,QAAQ,CAAC,EAClB,IAAI,EAAE,CAAC,EACP,KAAK,EAAC,MAAM,EACZ,mBAAmB,EAAE,EAAE,EACvB,mBAAmB,EAAE,IAAI,EACzB,QAAQ,EAAE,CAAC,CAAM,EAAE,EAAE;YACnB,MAAM,EAAE,aAAa,EAAE,iBAAiB,EAAE,WAAW,EAAE,GAAG,CAAC,CAAC,WAAW,CAAA;YACvE,MAAM,KAAK,GAAU;gBACnB,MAAM,EAAE,aAAa,CAAC,CAAC;gBACvB,QAAQ,EAAE,iBAAiB,CAAC,MAAM;gBAClC,OAAO,EAAE,WAAW,CAAC,MAAM;aAC5B,CAAA;YACD,MAAM,CAAC,OAAO,GAAG,MAAM,CAAC,KAAK,EAAE,KAAK,CAAC,CAAA;YACrC,QAAQ,EAAE,CAAC,CAAC,EAAE,EAAE,MAAM,EAAE,MAAM,CAAC,OAAO,EAAE,KAAK,EAAE,CAAC,CAAA;QAClD,CAAC,KACG,KAAK,YAET,KAAC,MAAM,OACD,IAAI,CAAC,eAAe,CAAC,EACzB,KAAK,EAAC,MAAM,EACZ,GAAG,EAAE,GAAG,EACR,EAAE,EAAE,GAAG,KACH,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,QAAiB,EAAE,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,KACzE,MAAM,YAET,QAAQ,GACF,GACE,CACd,CAAA;AACH,CAAC,CAAC,CAAA"}
@@ -1,6 +1,6 @@
1
1
  'use client';"use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.SourceChip = exports.Sources = exports.Failure = exports.Step = exports.Code = exports.Aside = exports.AsideToggle = exports.ShareButton = exports.HeaderButton = exports.Header = exports.SidebarUser = exports.SidebarFolder = exports.SidebarItem = exports.SidebarSection = exports.SidebarScroll = exports.SidebarNewChat = exports.SidebarIconButton = exports.SidebarHeader = exports.Sidebar = exports.SLACK = exports.pinned = exports.sends = exports.ready = exports.Thread = exports.Message = exports.Caret = exports.ASK = exports.Composer = void 0;
3
+ exports.SourceCard = exports.Sources = exports.Failure = exports.Step = exports.Code = exports.Aside = exports.AsideToggle = exports.ShareButton = exports.HeaderButton = exports.Header = exports.SidebarUser = exports.SidebarFolder = exports.SidebarItem = exports.SidebarSection = exports.SidebarScroll = exports.SidebarNewChat = exports.SidebarIconButton = exports.SidebarHeader = exports.Sidebar = exports.SLACK = exports.pinned = exports.sends = exports.ready = exports.Thread = exports.Message = exports.Caret = exports.ASK = exports.Composer = void 0;
4
4
  /**
5
5
  * @hanzo/ui/chat — the one chat shell, on @hanzo/gui primitives.
6
6
  *
@@ -42,6 +42,23 @@ exports.SourceChip = exports.Sources = exports.Failure = exports.Step = exports.
42
42
  * active-id resolution. The same components serve a live conversation, a shared
43
43
  * read-only transcript and a test fixture.
44
44
  *
45
+ * Every component here spreads its residual props onto its own element, and
46
+ * `Message`, `Thread` and `Composer` publish a way through to the part they own
47
+ * (`body`, `column`, `field`). They did not, and that closed list was the ONE
48
+ * thing keeping hanzo/chat from adopting `Message`: its scroll machinery
49
+ * addresses turns by `id`, `SelectionAsk` finds one with
50
+ * `closest('.message-render')`, and its turn wears a fleet-wide `glass`
51
+ * material against a `bg` welded to `$color3`. None of it exotic, all of it
52
+ * free with a spread — gui forwards what it does not recognise, which is the
53
+ * mechanism `slot()` is already built on.
54
+ *
55
+ * `@hanzo/ui/chat/pure` is the same module's decisions with nothing attached:
56
+ * `sends`, `ready`, `pinned`, `SLACK`. It exists because `import('@hanzo/ui/chat')`
57
+ * cannot load in Node, so no script and no spec could reach them. They are also
58
+ * re-exported from `@hanzo/ui/product/pure`, because they are not chat rules —
59
+ * ten of the fifteen files importing `sends` in hanzo.app are a SQL editor, a
60
+ * file explorer, a command palette, a rename field.
61
+ *
45
62
  * Styling is `$` tokens throughout, never literal colours, so each brand retunes
46
63
  * through its own theme and nothing here carries a Hanzo mark — which is what
47
64
  * lets the same shell ship on Lux and Zoo surfaces without leaking a brand.
@@ -93,4 +110,4 @@ var Failure_1 = require("./Failure.cjs");
93
110
  Object.defineProperty(exports, "Failure", { enumerable: true, get: function () { return Failure_1.Failure; } });
94
111
  var Sources_1 = require("./Sources.cjs");
95
112
  Object.defineProperty(exports, "Sources", { enumerable: true, get: function () { return Sources_1.Sources; } });
96
- Object.defineProperty(exports, "SourceChip", { enumerable: true, get: function () { return Sources_1.SourceChip; } });
113
+ Object.defineProperty(exports, "SourceCard", { enumerable: true, get: function () { return Sources_1.SourceCard; } });