From b21d40d084254ac21545396545e0428c0b06b7e7 Mon Sep 17 00:00:00 2001
From: Jeff <158072326+jeffrey701@users.noreply.github.com>
Date: Mon, 27 Jul 2026 10:35:22 +0200
Subject: [PATCH] feat(ui-kit): relocate streaming-text, use-streaming-text,
typing-indicator from miner-ui
---
.../src/chat-message-components.test.tsx | 2 +-
.../src/components/chat/conversation.tsx | 4 +--
.../src/components/chat/message-list.tsx | 2 +-
packages/loopover-ui-kit/package.json | 4 +++
.../src/components}/streaming-text.test.tsx | 17 +++++++----
.../src/components/streaming-text.tsx | 30 +++++++++++++++----
.../src/components}/typing-indicator.test.tsx | 0
.../src/components}/typing-indicator.tsx | 7 ++++-
.../src/hooks}/use-streaming-text.test.ts | 20 +++++++++----
.../src/hooks}/use-streaming-text.ts | 7 +++--
10 files changed, 70 insertions(+), 23 deletions(-)
rename {apps/loopover-miner-ui/src => packages/loopover-ui-kit/src/components}/streaming-text.test.tsx (88%)
rename {apps/loopover-miner-ui => packages/loopover-ui-kit}/src/components/streaming-text.tsx (72%)
rename {apps/loopover-miner-ui/src/components/chat => packages/loopover-ui-kit/src/components}/typing-indicator.test.tsx (100%)
rename {apps/loopover-miner-ui/src/components/chat => packages/loopover-ui-kit/src/components}/typing-indicator.tsx (89%)
rename {apps/loopover-miner-ui/src => packages/loopover-ui-kit/src/hooks}/use-streaming-text.test.ts (90%)
rename {apps/loopover-miner-ui/src/lib => packages/loopover-ui-kit/src/hooks}/use-streaming-text.ts (94%)
diff --git a/apps/loopover-miner-ui/src/chat-message-components.test.tsx b/apps/loopover-miner-ui/src/chat-message-components.test.tsx
index 46c99a5ac7..330e9048cb 100644
--- a/apps/loopover-miner-ui/src/chat-message-components.test.tsx
+++ b/apps/loopover-miner-ui/src/chat-message-components.test.tsx
@@ -2,7 +2,7 @@ import { render, screen } from "@testing-library/react";
import { describe, expect, it } from "vitest";
import { MessageList } from "./components/chat/message-list";
import { MessageBubble } from "./components/chat/message-bubble";
-import { TypingIndicator } from "./components/chat/typing-indicator";
+import { TypingIndicator } from "@loopover/ui-kit/components/typing-indicator";
import { emptyConversation, multiTurnConversation, singleMessage, type ChatMessage } from "./components/chat/fixtures";
describe("MessageList (#6515) — StateBoundary branches", () => {
diff --git a/apps/loopover-miner-ui/src/components/chat/conversation.tsx b/apps/loopover-miner-ui/src/components/chat/conversation.tsx
index 2a2489a4fe..57c8357e32 100644
--- a/apps/loopover-miner-ui/src/components/chat/conversation.tsx
+++ b/apps/loopover-miner-ui/src/components/chat/conversation.tsx
@@ -3,10 +3,10 @@ import { useCallback, useRef, useState } from "react";
import { Avatar, AvatarFallback } from "@loopover/ui-kit/components/avatar";
import { ChatComposer } from "@/components/chat-composer";
-import { StreamingText } from "@/components/streaming-text";
+import { StreamingText } from "@loopover/ui-kit/components/streaming-text";
import { MessageList } from "@/components/chat/message-list";
import type { ChatMessage } from "@/components/chat/fixtures";
-import type { ChunkSource } from "@/lib/use-streaming-text";
+import type { ChunkSource } from "@loopover/ui-kit/hooks/use-streaming-text";
import { streamChat, type ChatWireMessage } from "@/lib/chat-stream";
import {
handlePortfolioQueueChatCommand,
diff --git a/apps/loopover-miner-ui/src/components/chat/message-list.tsx b/apps/loopover-miner-ui/src/components/chat/message-list.tsx
index 94ae1c6051..157b7429fb 100644
--- a/apps/loopover-miner-ui/src/components/chat/message-list.tsx
+++ b/apps/loopover-miner-ui/src/components/chat/message-list.tsx
@@ -3,7 +3,7 @@ import { ScrollArea } from "@loopover/ui-kit/components/scroll-area";
import { StateBoundary } from "@loopover/ui-kit/components/state-views";
import { isChatViewportNearBottom, scrollChatViewportToBottom } from "@/lib/chat-scroll";
import { MessageBubble } from "./message-bubble";
-import { TypingIndicator } from "./typing-indicator";
+import { TypingIndicator } from "@loopover/ui-kit/components/typing-indicator";
import type { ChatMessage } from "./fixtures";
// The scrollable message list for the chat rail (#6515). Backend-agnostic: it renders whatever message
diff --git a/packages/loopover-ui-kit/package.json b/packages/loopover-ui-kit/package.json
index c0a7eb6196..17455afb80 100644
--- a/packages/loopover-ui-kit/package.json
+++ b/packages/loopover-ui-kit/package.json
@@ -32,6 +32,10 @@
"types": "./dist/hooks/use-mobile.d.ts",
"default": "./dist/hooks/use-mobile.js"
},
+ "./hooks/use-streaming-text": {
+ "types": "./dist/hooks/use-streaming-text.d.ts",
+ "default": "./dist/hooks/use-streaming-text.js"
+ },
"./components/*": {
"types": "./dist/components/*.d.ts",
"default": "./dist/components/*.js"
diff --git a/apps/loopover-miner-ui/src/streaming-text.test.tsx b/packages/loopover-ui-kit/src/components/streaming-text.test.tsx
similarity index 88%
rename from apps/loopover-miner-ui/src/streaming-text.test.tsx
rename to packages/loopover-ui-kit/src/components/streaming-text.test.tsx
index 654d5960ec..2cf17ac12b 100644
--- a/apps/loopover-miner-ui/src/streaming-text.test.tsx
+++ b/packages/loopover-ui-kit/src/components/streaming-text.test.tsx
@@ -1,7 +1,7 @@
import { act, render, screen, waitFor } from "@testing-library/react";
import { afterEach, describe, expect, it, vi } from "vitest";
-import { StreamingText } from "./components/streaming-text";
-import type { ChunkSource } from "./lib/use-streaming-text";
+import { StreamingText } from "./streaming-text";
+import type { ChunkSource } from "../hooks/use-streaming-text";
afterEach(() => vi.unstubAllGlobals());
@@ -39,7 +39,8 @@ function deferredSource() {
}
return {
source: (() => gen()) as ChunkSource,
- push: async (chunk: string) => act(async () => (queued.push(chunk), wake())),
+ push: async (chunk: string) =>
+ act(async () => (queued.push(chunk), wake())),
finish: async () => act(async () => ((finished = true), wake())),
};
}
@@ -50,7 +51,9 @@ describe("StreamingText (#6516)", () => {
it("renders an idle paragraph with no text when given no source", () => {
mockReducedMotion(false);
const { container } = render(
+
{text} {status === "streaming" && !reducedMotion ? ( -