@tangle-network/ui 11.51.11 → 11.51.13
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/CHANGELOG.md +12 -0
- package/dist/chat/chat-message.js +1 -1
- package/dist/chat/user-message.js +1 -1
- package/dist/hooks/use-event-stream.d.ts +1 -1
- package/dist/hooks/use-event-stream.js +2 -2
- package/package.json +1 -1
- package/src/chat/chat-message.tsx +1 -1
- package/src/chat/message-author.test.tsx +6 -6
- package/src/chat/speaker.stories.tsx +1 -1
- package/src/chat/synthetic-text.test.tsx +2 -2
- package/src/chat/user-message.tsx +1 -1
- package/src/hooks/use-event-stream.ts +3 -3
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,15 @@
|
|
|
1
|
+
## 11.51.13
|
|
2
|
+
|
|
3
|
+
### Patch Changes
|
|
4
|
+
|
|
5
|
+
- 919beef: The user's turn in `UserMessage` and `ChatMessage` is an indigo tint (`bg-primary/15`, foreground text) instead of the inverted foreground bubble, which rendered near-white in dark mode beside dark transcripts (run-page panel, 2026-11-04).
|
|
6
|
+
|
|
7
|
+
## 11.51.12
|
|
8
|
+
|
|
9
|
+
### Patch Changes
|
|
10
|
+
|
|
11
|
+
- e5f85bd: Preserve sequence zero when a named native stream reconnects; omit the cursor before any event arrives.
|
|
12
|
+
|
|
1
13
|
## 11.51.11
|
|
2
14
|
|
|
3
15
|
- Keep tool-row actions and navigation reachable after a hover preview. HoverCard uses its anchor for placement, preserves keyboard inspection, and dismisses previews when a nested control commits an action. Touch actions do not become hover intent.
|
|
@@ -32,7 +32,7 @@ function ChatMessage({ role, content, toolCalls, isStreaming, timestamp, classNa
|
|
|
32
32
|
children: formatUtcTime(timestamp)
|
|
33
33
|
})]
|
|
34
34
|
}), /* @__PURE__ */ jsxs("div", {
|
|
35
|
-
className: cn("min-w-0 max-w-[85%] space-y-1", fromViewer ? "rounded-[var(--radius-lg)] bg-
|
|
35
|
+
className: cn("min-w-0 max-w-[85%] space-y-1", fromViewer ? "rounded-[var(--radius-lg)] bg-primary/15 px-[var(--chat-message-px)] py-[var(--chat-message-py)] text-foreground" : fromOtherPerson ? "rounded-[var(--radius-lg)] border border-border bg-card px-[var(--chat-message-px)] py-[var(--chat-message-py)] text-foreground" : isAssistant ? "text-foreground" : "rounded-[var(--radius-lg)] border border-border bg-card px-[var(--chat-message-px)] py-[var(--chat-message-py)]"),
|
|
36
36
|
children: [isUser ? /* @__PURE__ */ jsx("div", {
|
|
37
37
|
className: cn("whitespace-pre-wrap", TRANSCRIPT_TEXT),
|
|
38
38
|
children: content
|
|
@@ -61,7 +61,7 @@ function UserBubble({ text, timestamp, actions, lines }) {
|
|
|
61
61
|
children: /* @__PURE__ */ jsxs("div", {
|
|
62
62
|
className: "group flex min-w-0 max-w-[78%] flex-col items-end gap-2",
|
|
63
63
|
children: [/* @__PURE__ */ jsxs("div", {
|
|
64
|
-
className: "relative w-full min-w-0 rounded-2xl bg-
|
|
64
|
+
className: "relative w-full min-w-0 rounded-2xl bg-primary/15 px-4 py-3 text-foreground",
|
|
65
65
|
children: [timestamp ? /* @__PURE__ */ jsx("span", {
|
|
66
66
|
"data-user-message-time": "",
|
|
67
67
|
className: "pointer-events-none absolute right-full top-1/2 mr-3 -translate-y-1/2 whitespace-nowrap text-sm text-muted-foreground opacity-0 transition-opacity group-hover:opacity-100",
|
|
@@ -12,4 +12,4 @@ export interface EventStreamHandlers {
|
|
|
12
12
|
onLiveChange?: (live: boolean) => void;
|
|
13
13
|
}
|
|
14
14
|
/** Producer-owned named events; host transports keep their authenticated reconnect lifecycle. */
|
|
15
|
-
export declare function useEventStream(url: string | undefined, handlers: EventStreamHandlers): void;
|
|
15
|
+
export declare function useEventStream(url: string | (() => string) | undefined, handlers: EventStreamHandlers): void;
|
|
@@ -8,7 +8,7 @@ function openNativeStream(url, handlers) {
|
|
|
8
8
|
const open = () => {
|
|
9
9
|
handlers.onConnecting?.(attempt);
|
|
10
10
|
const target = new URL(typeof url === "function" ? url() : url, globalThis.location?.href), after = handlers.after?.();
|
|
11
|
-
if (after !== void 0 && after
|
|
11
|
+
if (after !== void 0 && after >= 0) target.searchParams.set("after", String(after));
|
|
12
12
|
let connection;
|
|
13
13
|
const live = (value) => {
|
|
14
14
|
if (!stopped && (!connection || source === connection)) handlers.onLiveChange?.(value);
|
|
@@ -56,7 +56,7 @@ function useEventStream(url, handlers) {
|
|
|
56
56
|
const connection = openNativeStream(url, {
|
|
57
57
|
opener: handlers.opener,
|
|
58
58
|
events: Object.fromEntries(Object.keys(handlers.events).map((name) => [name, (event) => latest.current.events[name]?.(event)])),
|
|
59
|
-
after: () => latest.current.after?.() ??
|
|
59
|
+
after: () => latest.current.after?.() ?? -1,
|
|
60
60
|
onStart: () => latest.current.onStart?.(),
|
|
61
61
|
onLiveChange: (live) => latest.current.onLiveChange?.(live),
|
|
62
62
|
terminalEvent: "end"
|
package/package.json
CHANGED
|
@@ -95,7 +95,7 @@ export function ChatMessage({
|
|
|
95
95
|
className={cn(
|
|
96
96
|
"min-w-0 max-w-[85%] space-y-1",
|
|
97
97
|
fromViewer
|
|
98
|
-
? "rounded-[var(--radius-lg)] bg-
|
|
98
|
+
? "rounded-[var(--radius-lg)] bg-primary/15 px-[var(--chat-message-px)] py-[var(--chat-message-py)] text-foreground"
|
|
99
99
|
: fromOtherPerson
|
|
100
100
|
? "rounded-[var(--radius-lg)] border border-border bg-card px-[var(--chat-message-px)] py-[var(--chat-message-py)] text-foreground"
|
|
101
101
|
: isAssistant
|
|
@@ -23,12 +23,12 @@ const segmentedParts: SessionPart[] = [
|
|
|
23
23
|
describe("a transcript without authors", () => {
|
|
24
24
|
|
|
25
25
|
it("renders UserMessage actions and application notes as before", () => {
|
|
26
|
-
expect(html(<UserMessage content="Can I break my lease early?" actions={<button type="button">Copy</button>} />)).toMatchInlineSnapshot(`"<div class="flex justify-end"><div class="group flex min-w-0 max-w-[78%] flex-col items-end gap-2"><div class="relative w-full min-w-0 rounded-2xl bg-
|
|
27
|
-
expect(html(<UserMessage parts={segmentedParts} actions={<button type="button">Copy</button>} />)).toMatchInlineSnapshot(`"<div class="space-y-2"><div class="flex justify-end"><div class="group flex min-w-0 max-w-[78%] flex-col items-end gap-2"><div class="relative w-full min-w-0 rounded-2xl bg-
|
|
26
|
+
expect(html(<UserMessage content="Can I break my lease early?" actions={<button type="button">Copy</button>} />)).toMatchInlineSnapshot(`"<div class="flex justify-end"><div class="group flex min-w-0 max-w-[78%] flex-col items-end gap-2"><div class="relative w-full min-w-0 rounded-2xl bg-primary/15 px-4 py-3 text-foreground"><div class="break-words whitespace-pre-wrap tangle-transcript-text text-[length:var(--transcript-font-size)] leading-[var(--transcript-line-height)]">Can I break my lease early?</div></div><div class="flex flex-wrap items-center justify-end gap-1.5 text-sm text-muted-foreground"><button type="button">Copy</button></div></div></div>"`)
|
|
27
|
+
expect(html(<UserMessage parts={segmentedParts} actions={<button type="button">Copy</button>} />)).toMatchInlineSnapshot(`"<div class="space-y-2"><div class="flex justify-end"><div class="group flex min-w-0 max-w-[78%] flex-col items-end gap-2"><div class="relative w-full min-w-0 rounded-2xl bg-primary/15 px-4 py-3 text-foreground"><div class="break-words whitespace-pre-wrap tangle-transcript-text text-[length:var(--transcript-font-size)] leading-[var(--transcript-line-height)]">I signed the lease in March.</div></div></div></div><div role="note" aria-label="Application note" data-synthetic-text="" class="min-w-0 rounded-md border border-dashed border-border bg-muted/30 px-3 py-2 text-sm text-muted-foreground"><div class="mb-1 font-medium">Application note</div><div class="break-words whitespace-pre-wrap">Lease uploaded: lease.pdf</div></div><div class="flex justify-end"><div class="group flex min-w-0 max-w-[78%] flex-col items-end gap-2"><div class="relative w-full min-w-0 rounded-2xl bg-primary/15 px-4 py-3 text-foreground"><div class="break-words whitespace-pre-wrap tangle-transcript-text text-[length:var(--transcript-font-size)] leading-[var(--transcript-line-height)]">Here it is.</div></div></div></div><div class="flex flex-wrap items-center justify-end gap-1.5 text-sm text-muted-foreground"><button type="button">Copy</button></div></div>"`)
|
|
28
28
|
})
|
|
29
29
|
|
|
30
30
|
it("renders UserMessage as before", () => {
|
|
31
|
-
expect(html(<UserMessage content="Can I break my lease early?" />)).toMatchInlineSnapshot(`"<div class="flex justify-end"><div class="group flex min-w-0 max-w-[78%] flex-col items-end gap-2"><div class="relative w-full min-w-0 rounded-2xl bg-
|
|
31
|
+
expect(html(<UserMessage content="Can I break my lease early?" />)).toMatchInlineSnapshot(`"<div class="flex justify-end"><div class="group flex min-w-0 max-w-[78%] flex-col items-end gap-2"><div class="relative w-full min-w-0 rounded-2xl bg-primary/15 px-4 py-3 text-foreground"><div class="break-words whitespace-pre-wrap tangle-transcript-text text-[length:var(--transcript-font-size)] leading-[var(--transcript-line-height)]">Can I break my lease early?</div></div></div></div>"`)
|
|
32
32
|
})
|
|
33
33
|
|
|
34
34
|
})
|
|
@@ -92,7 +92,7 @@ describe("UserMessage with an author", () => {
|
|
|
92
92
|
const { container } = render(<UserMessage content="Did you give a forwarding address?" author={attorney} viewerId={client.id} />)
|
|
93
93
|
expect(container.firstElementChild?.className).toContain("justify-start")
|
|
94
94
|
expect(bubbleOf("Did you give a forwarding address?").className).toContain("bg-card")
|
|
95
|
-
expect(bubbleOf("Did you give a forwarding address?").className).not.toContain("bg-
|
|
95
|
+
expect(bubbleOf("Did you give a forwarding address?").className).not.toContain("bg-primary/15")
|
|
96
96
|
expect(screen.getByText("Jane Doe")).toBeInTheDocument()
|
|
97
97
|
expect(screen.getByText("Attorney")).toBeInTheDocument()
|
|
98
98
|
})
|
|
@@ -148,7 +148,7 @@ describe("AgentTimeline with three participants", () => {
|
|
|
148
148
|
|
|
149
149
|
it("sets the client's own words apart from the attorney's on the client's screen", () => {
|
|
150
150
|
render(<AgentTimeline items={thread} viewerId={client.id} />)
|
|
151
|
-
expect(bubbleOf("My landlord kept my deposit.").className).toContain("bg-
|
|
151
|
+
expect(bubbleOf("My landlord kept my deposit.").className).toContain("bg-primary/15")
|
|
152
152
|
expect(screen.queryByText("Maria Lopez")).not.toBeInTheDocument()
|
|
153
153
|
expect(bubbleOf("Did you give a forwarding address?").className).toContain("bg-card")
|
|
154
154
|
expect(screen.getByText("Jane Doe")).toBeInTheDocument()
|
|
@@ -156,7 +156,7 @@ describe("AgentTimeline with three participants", () => {
|
|
|
156
156
|
|
|
157
157
|
it("mirrors the thread on the attorney's screen", () => {
|
|
158
158
|
render(<AgentTimeline items={thread} viewerId={attorney.id} />)
|
|
159
|
-
expect(bubbleOf("Did you give a forwarding address?").className).toContain("bg-
|
|
159
|
+
expect(bubbleOf("Did you give a forwarding address?").className).toContain("bg-primary/15")
|
|
160
160
|
expect(bubbleOf("My landlord kept my deposit.").className).toContain("bg-card")
|
|
161
161
|
expect(screen.getByText("Maria Lopez")).toBeInTheDocument()
|
|
162
162
|
expect(screen.queryByText("Jane Doe")).not.toBeInTheDocument()
|
|
@@ -12,7 +12,7 @@ type Part =
|
|
|
12
12
|
function Turn({ parts, live = false }: { parts: Part[]; live?: boolean }) {
|
|
13
13
|
return (
|
|
14
14
|
<div className="mx-auto flex w-full max-w-3xl flex-col gap-6 px-4 py-6">
|
|
15
|
-
<p className={`ml-auto w-fit max-w-[78%] rounded-2xl bg-
|
|
15
|
+
<p className={`ml-auto w-fit max-w-[78%] rounded-2xl bg-primary/15 px-4 py-3 text-foreground ${TRANSCRIPT_TEXT}`}>
|
|
16
16
|
@writer @research-lead plan the launch announcement for the roster feature.
|
|
17
17
|
</p>
|
|
18
18
|
{partitionBySpeaker(parts).map(({ speaker, parts: own }) => (
|
|
@@ -38,7 +38,7 @@ describe("user text boundaries", () => {
|
|
|
38
38
|
|
|
39
39
|
it("preserves a single joined bubble when no application note is present", () => {
|
|
40
40
|
const { container } = render(<UserMessage parts={mixed.slice(0, 2)} />);
|
|
41
|
-
expect(container.querySelectorAll(".bg-
|
|
41
|
+
expect(container.querySelectorAll(".bg-primary\\/15")).toHaveLength(1);
|
|
42
42
|
expect(container.querySelector(".whitespace-pre-wrap")?.textContent).toBe("First\nSecond");
|
|
43
43
|
expect(screen.queryByRole("note")).not.toBeInTheDocument();
|
|
44
44
|
});
|
|
@@ -61,7 +61,7 @@ describe("user text boundaries", () => {
|
|
|
61
61
|
const { container } = render(<UserMessage parts={[mixed[2]]} actions={<button type="button">Inspect receipt</button>} />);
|
|
62
62
|
expect(screen.getByRole("note")).toHaveTextContent("Receipt");
|
|
63
63
|
expect(screen.getByRole("button", { name: "Inspect receipt" })).toBeInTheDocument();
|
|
64
|
-
expect(container.querySelector(".bg-
|
|
64
|
+
expect(container.querySelector(".bg-primary\\/15")).toBeNull();
|
|
65
65
|
});
|
|
66
66
|
|
|
67
67
|
it("retains direct-content timestamps", () => {
|
|
@@ -96,7 +96,7 @@ function UserBubble({ text, timestamp, actions, lines }: {
|
|
|
96
96
|
return (
|
|
97
97
|
<div className="flex justify-end">
|
|
98
98
|
<div className="group flex min-w-0 max-w-[78%] flex-col items-end gap-2">
|
|
99
|
-
<div className="relative w-full min-w-0 rounded-2xl bg-
|
|
99
|
+
<div className="relative w-full min-w-0 rounded-2xl bg-primary/15 px-4 py-3 text-foreground">
|
|
100
100
|
{timestamp ? (
|
|
101
101
|
<span
|
|
102
102
|
data-user-message-time=""
|
|
@@ -25,7 +25,7 @@ export function openNativeStream(url: string | (() => string), handlers: NativeP
|
|
|
25
25
|
const open = () => {
|
|
26
26
|
handlers.onConnecting?.(attempt)
|
|
27
27
|
const target = new URL(typeof url === 'function' ? url() : url, globalThis.location?.href), after = handlers.after?.()
|
|
28
|
-
if (after !== undefined && after
|
|
28
|
+
if (after !== undefined && after >= 0) target.searchParams.set('after', String(after))
|
|
29
29
|
let connection: EventStreamSource
|
|
30
30
|
const live = (value: boolean) => { if (!stopped && (!connection || source === connection)) handlers.onLiveChange?.(value) }
|
|
31
31
|
connection = handlers.opener ? handlers.opener(target.toString(), live) : new EventSource(target.toString())
|
|
@@ -52,7 +52,7 @@ export function openNativeStream(url: string | (() => string), handlers: NativeP
|
|
|
52
52
|
}
|
|
53
53
|
|
|
54
54
|
/** Producer-owned named events; host transports keep their authenticated reconnect lifecycle. */
|
|
55
|
-
export function useEventStream(url: string | undefined, handlers: EventStreamHandlers): void {
|
|
55
|
+
export function useEventStream(url: string | (() => string) | undefined, handlers: EventStreamHandlers): void {
|
|
56
56
|
const latest = useRef(handlers)
|
|
57
57
|
latest.current = handlers
|
|
58
58
|
const names = Object.keys(handlers.events).sort().join(',')
|
|
@@ -61,7 +61,7 @@ export function useEventStream(url: string | undefined, handlers: EventStreamHan
|
|
|
61
61
|
const current: EventStreamHandlers = {
|
|
62
62
|
opener: handlers.opener,
|
|
63
63
|
events: Object.fromEntries(Object.keys(handlers.events).map(name => [name, (event: MessageEvent<string>) => latest.current.events[name]?.(event)])),
|
|
64
|
-
after: () => latest.current.after?.() ??
|
|
64
|
+
after: () => latest.current.after?.() ?? -1,
|
|
65
65
|
onStart: () => latest.current.onStart?.(),
|
|
66
66
|
onLiveChange: live => latest.current.onLiveChange?.(live),
|
|
67
67
|
}
|