From 5d18fb7b02254287660e718f9afa767d8ddd8bf1 Mon Sep 17 00:00:00 2001 From: gdemonc <1502689916@qq.com> Date: Sat, 11 Apr 2026 16:57:17 +0800 Subject: [PATCH 01/19] feat(desktop-shell-ball): add bubble message feed contract --- .../features/shell-ball/shellBall.bubble.ts | 14 ++++++ .../shell-ball/shellBall.contract.test.ts | 48 +++++++++++++++++++ .../shell-ball/shellBall.windowSync.ts | 5 ++ 3 files changed, 67 insertions(+) create mode 100644 apps/desktop/src/features/shell-ball/shellBall.bubble.ts diff --git a/apps/desktop/src/features/shell-ball/shellBall.bubble.ts b/apps/desktop/src/features/shell-ball/shellBall.bubble.ts new file mode 100644 index 000000000..ad6fcf187 --- /dev/null +++ b/apps/desktop/src/features/shell-ball/shellBall.bubble.ts @@ -0,0 +1,14 @@ +export type ShellBallBubbleMessageRole = "assistant" | "user" | "system"; + +export type ShellBallBubbleMessageFreshnessHint = "fresh" | "stale"; + +export type ShellBallBubbleMessageMotionHint = "pulse" | "settle"; + +export type ShellBallBubbleMessage = { + id: string; + role: ShellBallBubbleMessageRole; + text: string; + createdAt: string; + freshnessHint?: ShellBallBubbleMessageFreshnessHint; + motionHint?: ShellBallBubbleMessageMotionHint; +}; diff --git a/apps/desktop/src/features/shell-ball/shellBall.contract.test.ts b/apps/desktop/src/features/shell-ball/shellBall.contract.test.ts index a150cadd6..b52f65e7d 100644 --- a/apps/desktop/src/features/shell-ball/shellBall.contract.test.ts +++ b/apps/desktop/src/features/shell-ball/shellBall.contract.test.ts @@ -53,6 +53,7 @@ import { getShellBallHelperWindowVisibility, shellBallWindowSyncEvents, } from "./shellBall.windowSync"; +import type { ShellBallBubbleMessage } from "./shellBall.bubble"; import { SHELL_BALL_WINDOW_GAP_PX, SHELL_BALL_WINDOW_SAFE_MARGIN_PX, @@ -1042,12 +1043,32 @@ test("shell-ball helper window sync maps visual states into visibility and snaps visualState: "voice_locked", inputValue: "draft", voicePreview: "lock", + bubbleMessages: [ + { + id: "msg-1", + role: "assistant", + text: "Still listening.", + createdAt: "2026-04-11T10:00:00.000Z", + freshnessHint: "fresh", + motionHint: "pulse", + }, + ], }), { visualState: "voice_locked", inputBarMode: "voice", inputValue: "draft", voicePreview: "lock", + bubbleMessages: [ + { + id: "msg-1", + role: "assistant", + text: "Still listening.", + createdAt: "2026-04-11T10:00:00.000Z", + freshnessHint: "fresh", + motionHint: "pulse", + }, + ], visibility: { bubble: true, input: true, @@ -1056,6 +1077,33 @@ test("shell-ball helper window sync maps visual states into visibility and snaps ); }); +test("shell-ball bubble message contract supports local bubble feed hints", () => { + const bubbleMessage: ShellBallBubbleMessage = { + id: "msg-local-1", + role: "user", + text: "Open the dashboard.", + createdAt: "2026-04-11T10:00:00.000Z", + freshnessHint: "stale", + motionHint: "settle", + }; + + assert.deepEqual(bubbleMessage, { + id: "msg-local-1", + role: "user", + text: "Open the dashboard.", + createdAt: "2026-04-11T10:00:00.000Z", + freshnessHint: "stale", + motionHint: "settle", + }); + + assert.deepEqual(createShellBallWindowSnapshot({ + visualState: "idle", + inputValue: "", + voicePreview: null, + bubbleMessages: [], + }).bubbleMessages, []); +}); + test("shell-ball window metrics compute safe frames and helper anchors", () => { assert.equal(SHELL_BALL_WINDOW_GAP_PX, 12); assert.equal(SHELL_BALL_WINDOW_SAFE_MARGIN_PX, 12); diff --git a/apps/desktop/src/features/shell-ball/shellBall.windowSync.ts b/apps/desktop/src/features/shell-ball/shellBall.windowSync.ts index 3c3aabbf2..8cae776a2 100644 --- a/apps/desktop/src/features/shell-ball/shellBall.windowSync.ts +++ b/apps/desktop/src/features/shell-ball/shellBall.windowSync.ts @@ -1,3 +1,4 @@ +import type { ShellBallBubbleMessage } from "./shellBall.bubble"; import type { ShellBallVoicePreview } from "./shellBall.interaction"; import { getShellBallInputBarMode } from "./shellBall.interaction"; import type { ShellBallInputBarMode, ShellBallVisualState } from "./shellBall.types"; @@ -26,6 +27,7 @@ export type ShellBallWindowSnapshot = { inputBarMode: ShellBallInputBarMode; inputValue: string; voicePreview: ShellBallVoicePreview; + bubbleMessages: ShellBallBubbleMessage[]; visibility: ShellBallHelperWindowVisibility; }; @@ -79,12 +81,14 @@ export function createShellBallWindowSnapshot(input: { visualState: ShellBallVisualState; inputValue: string; voicePreview: ShellBallVoicePreview; + bubbleMessages?: ShellBallBubbleMessage[]; }): ShellBallWindowSnapshot { return { visualState: input.visualState, inputBarMode: getShellBallInputBarMode(input.visualState), inputValue: input.inputValue, voicePreview: input.voicePreview, + bubbleMessages: input.bubbleMessages ?? [], visibility: getShellBallHelperWindowVisibility(input.visualState), }; } @@ -94,5 +98,6 @@ export function createDefaultShellBallWindowSnapshot(): ShellBallWindowSnapshot visualState: "idle", inputValue: "", voicePreview: null, + bubbleMessages: [], }); } From 2da1079aa04efa85d2b8cef75680113f01954954 Mon Sep 17 00:00:00 2001 From: gdemonc <1502689916@qq.com> Date: Sat, 11 Apr 2026 16:58:51 +0800 Subject: [PATCH 02/19] fix(desktop-shell-ball): align bubble message roles --- apps/desktop/src/features/shell-ball/shellBall.bubble.ts | 2 +- .../src/features/shell-ball/shellBall.contract.test.ts | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/desktop/src/features/shell-ball/shellBall.bubble.ts b/apps/desktop/src/features/shell-ball/shellBall.bubble.ts index ad6fcf187..a01909af7 100644 --- a/apps/desktop/src/features/shell-ball/shellBall.bubble.ts +++ b/apps/desktop/src/features/shell-ball/shellBall.bubble.ts @@ -1,4 +1,4 @@ -export type ShellBallBubbleMessageRole = "assistant" | "user" | "system"; +export type ShellBallBubbleMessageRole = "user" | "agent"; export type ShellBallBubbleMessageFreshnessHint = "fresh" | "stale"; diff --git a/apps/desktop/src/features/shell-ball/shellBall.contract.test.ts b/apps/desktop/src/features/shell-ball/shellBall.contract.test.ts index b52f65e7d..c5b42d7ed 100644 --- a/apps/desktop/src/features/shell-ball/shellBall.contract.test.ts +++ b/apps/desktop/src/features/shell-ball/shellBall.contract.test.ts @@ -1046,7 +1046,7 @@ test("shell-ball helper window sync maps visual states into visibility and snaps bubbleMessages: [ { id: "msg-1", - role: "assistant", + role: "agent", text: "Still listening.", createdAt: "2026-04-11T10:00:00.000Z", freshnessHint: "fresh", @@ -1062,7 +1062,7 @@ test("shell-ball helper window sync maps visual states into visibility and snaps bubbleMessages: [ { id: "msg-1", - role: "assistant", + role: "agent", text: "Still listening.", createdAt: "2026-04-11T10:00:00.000Z", freshnessHint: "fresh", From cb45e060e4bcade54b02ce77e754e12a8aefd5a4 Mon Sep 17 00:00:00 2001 From: gdemonc <1502689916@qq.com> Date: Sat, 11 Apr 2026 17:02:46 +0800 Subject: [PATCH 03/19] fix(desktop-shell-ball): copy bubble snapshot messages --- .../features/shell-ball/shellBall.bubble.ts | 4 ++ .../shell-ball/shellBall.contract.test.ts | 51 +++++++++++++++++++ .../shell-ball/shellBall.windowSync.ts | 3 +- 3 files changed, 57 insertions(+), 1 deletion(-) diff --git a/apps/desktop/src/features/shell-ball/shellBall.bubble.ts b/apps/desktop/src/features/shell-ball/shellBall.bubble.ts index a01909af7..894e509ec 100644 --- a/apps/desktop/src/features/shell-ball/shellBall.bubble.ts +++ b/apps/desktop/src/features/shell-ball/shellBall.bubble.ts @@ -12,3 +12,7 @@ export type ShellBallBubbleMessage = { freshnessHint?: ShellBallBubbleMessageFreshnessHint; motionHint?: ShellBallBubbleMessageMotionHint; }; + +export function cloneShellBallBubbleMessages(messages: ShellBallBubbleMessage[]): ShellBallBubbleMessage[] { + return [...messages]; +} diff --git a/apps/desktop/src/features/shell-ball/shellBall.contract.test.ts b/apps/desktop/src/features/shell-ball/shellBall.contract.test.ts index c5b42d7ed..71ee137cb 100644 --- a/apps/desktop/src/features/shell-ball/shellBall.contract.test.ts +++ b/apps/desktop/src/features/shell-ball/shellBall.contract.test.ts @@ -1102,6 +1102,57 @@ test("shell-ball bubble message contract supports local bubble feed hints", () = voicePreview: null, bubbleMessages: [], }).bubbleMessages, []); + + const minimalBubbleMessage: ShellBallBubbleMessage = { + id: "msg-local-2", + role: "agent", + text: "On it.", + createdAt: "2026-04-11T10:01:00.000Z", + }; + + assert.deepEqual(minimalBubbleMessage, { + id: "msg-local-2", + role: "agent", + text: "On it.", + createdAt: "2026-04-11T10:01:00.000Z", + }); +}); + +test("shell-ball window snapshot copies bubble message arrays defensively", () => { + const sourceMessages: ShellBallBubbleMessage[] = [ + { + id: "msg-copy-1", + role: "agent", + text: "Drafting update.", + createdAt: "2026-04-11T10:02:00.000Z", + }, + ]; + + const snapshot = createShellBallWindowSnapshot({ + visualState: "hover_input", + inputValue: "draft", + voicePreview: null, + bubbleMessages: sourceMessages, + }); + + assert.notEqual(snapshot.bubbleMessages, sourceMessages); + assert.deepEqual(snapshot.bubbleMessages, sourceMessages); + + sourceMessages.push({ + id: "msg-copy-2", + role: "user", + text: "Keep going.", + createdAt: "2026-04-11T10:03:00.000Z", + }); + + assert.deepEqual(snapshot.bubbleMessages, [ + { + id: "msg-copy-1", + role: "agent", + text: "Drafting update.", + createdAt: "2026-04-11T10:02:00.000Z", + }, + ]); }); test("shell-ball window metrics compute safe frames and helper anchors", () => { diff --git a/apps/desktop/src/features/shell-ball/shellBall.windowSync.ts b/apps/desktop/src/features/shell-ball/shellBall.windowSync.ts index 8cae776a2..af413b6f8 100644 --- a/apps/desktop/src/features/shell-ball/shellBall.windowSync.ts +++ b/apps/desktop/src/features/shell-ball/shellBall.windowSync.ts @@ -1,3 +1,4 @@ +import { cloneShellBallBubbleMessages } from "./shellBall.bubble"; import type { ShellBallBubbleMessage } from "./shellBall.bubble"; import type { ShellBallVoicePreview } from "./shellBall.interaction"; import { getShellBallInputBarMode } from "./shellBall.interaction"; @@ -88,7 +89,7 @@ export function createShellBallWindowSnapshot(input: { inputBarMode: getShellBallInputBarMode(input.visualState), inputValue: input.inputValue, voicePreview: input.voicePreview, - bubbleMessages: input.bubbleMessages ?? [], + bubbleMessages: cloneShellBallBubbleMessages(input.bubbleMessages ?? []), visibility: getShellBallHelperWindowVisibility(input.visualState), }; } From b1e6855f666a91d767366e025277623f5e79cedc Mon Sep 17 00:00:00 2001 From: gdemonc <1502689916@qq.com> Date: Sat, 11 Apr 2026 17:07:05 +0800 Subject: [PATCH 04/19] feat(desktop-shell-ball): sync bubble messages to helper window --- .../shell-ball/ShellBallBubbleWindow.tsx | 2 +- .../components/ShellBallBubbleZone.tsx | 2 + .../shell-ball/shellBall.contract.test.ts | 171 ++++++++++++++++++ .../shell-ball/useShellBallCoordinator.ts | 17 ++ 4 files changed, 191 insertions(+), 1 deletion(-) diff --git a/apps/desktop/src/features/shell-ball/ShellBallBubbleWindow.tsx b/apps/desktop/src/features/shell-ball/ShellBallBubbleWindow.tsx index 7cbc29c71..68626afdc 100644 --- a/apps/desktop/src/features/shell-ball/ShellBallBubbleWindow.tsx +++ b/apps/desktop/src/features/shell-ball/ShellBallBubbleWindow.tsx @@ -17,7 +17,7 @@ export function ShellBallBubbleWindow({ visualState }: ShellBallBubbleWindowProp return (
- +
); } diff --git a/apps/desktop/src/features/shell-ball/components/ShellBallBubbleZone.tsx b/apps/desktop/src/features/shell-ball/components/ShellBallBubbleZone.tsx index ed67a0821..c40e9ca22 100644 --- a/apps/desktop/src/features/shell-ball/components/ShellBallBubbleZone.tsx +++ b/apps/desktop/src/features/shell-ball/components/ShellBallBubbleZone.tsx @@ -1,7 +1,9 @@ +import type { ShellBallBubbleMessage } from "../shellBall.bubble"; import type { ShellBallVisualState } from "../shellBall.types"; type ShellBallBubbleZoneProps = { visualState: ShellBallVisualState; + bubbleMessages?: ShellBallBubbleMessage[]; }; export function ShellBallBubbleZone({ visualState }: ShellBallBubbleZoneProps) { diff --git a/apps/desktop/src/features/shell-ball/shellBall.contract.test.ts b/apps/desktop/src/features/shell-ball/shellBall.contract.test.ts index 71ee137cb..7075135ab 100644 --- a/apps/desktop/src/features/shell-ball/shellBall.contract.test.ts +++ b/apps/desktop/src/features/shell-ball/shellBall.contract.test.ts @@ -363,6 +363,47 @@ function withDesktopAliasRuntime(callback: () => T) { } } +function withShellBallModuleRuntime( + moduleRelativePath: string, + mocks: Record, + callback: (moduleExports: Record) => T, +) { + const NodeModule = require("node:module") as any; + const originalLoad = NodeModule._load; + const modulePath = resolve(desktopRoot, "src/features/shell-ball", moduleRelativePath); + const source = readFileSync(modulePath, "utf8"); + const transpiledModule = { exports: {} as Record }; + const transpiled = ts.transpileModule(source, { + compilerOptions: { + jsx: ts.JsxEmit.ReactJSX, + module: ts.ModuleKind.CommonJS, + target: ts.ScriptTarget.ES2020, + esModuleInterop: true, + }, + fileName: modulePath, + }); + const moduleFactory = new Function("require", "module", "exports", transpiled.outputText) as ( + require: NodeRequire, + module: { exports: Record }, + exports: Record, + ) => void; + + NodeModule._load = function loadShellBallModule(request: string, parent: unknown, isMain: boolean) { + if (request in mocks) { + return mocks[request]; + } + + return originalLoad(request, parent, isMain); + }; + + try { + moduleFactory(require, transpiledModule, transpiledModule.exports); + return callback(transpiledModule.exports); + } finally { + NodeModule._load = originalLoad; + } +} + function withTrayControllerRuntime( openOrFocusDesktopWindow: (label: "dashboard" | "control-panel") => Promise, callback: (mod: { openControlPanelFromTray: () => Promise; calls: Array<"dashboard" | "control-panel"> }) => Promise | T, @@ -1927,6 +1968,136 @@ test("shell-ball bubble window owns the bubble zone rendering", () => { assert.doesNotMatch(markup, /shell-ball-input-bar/); }); +test("shell-ball coordinator snapshots carry shell-ball-local bubble messages", () => { + const { useShellBallCoordinator } = withShellBallModuleRuntime("useShellBallCoordinator.ts", { + react: { + useEffect() {}, + useMemo(factory: () => T) { + return factory(); + }, + useRef(value: T) { + return { current: value }; + }, + useState(value: T) { + return [typeof value === "function" ? (value as () => T)() : value, () => {}] as const; + }, + }, + "@tauri-apps/api/window": { + getCurrentWindow() { + return { label: shellBallWindowLabels.bubble }; + }, + }, + "../../platform/shellBallWindowController": { + shellBallWindowLabels, + }, + "./shellBall.windowSync": require(resolve(desktopRoot, ".cache/shell-ball-tests/features/shell-ball/shellBall.windowSync.js")), + }, (moduleExports) => moduleExports as { useShellBallCoordinator: typeof import("./useShellBallCoordinator").useShellBallCoordinator }); + + const { snapshot } = useShellBallCoordinator({ + visualState: "hover_input", + inputValue: "draft", + voicePreview: null, + setInputValue: () => {}, + onRegionEnter: () => {}, + onRegionLeave: () => {}, + onInputFocusChange: () => {}, + onSubmitText: () => {}, + onAttachFile: () => {}, + onPrimaryClick: () => {}, + }); + + assert.ok(Array.isArray(snapshot.bubbleMessages)); + assert.ok(snapshot.bubbleMessages.length > 0); +}); + +test("shell-ball bubble window resolves bubble messages from the helper-window snapshot", () => { + const helperSnapshot = createShellBallWindowSnapshot({ + visualState: "processing", + inputValue: "", + voicePreview: null, + bubbleMessages: [ + { + id: "msg-helper-1", + role: "agent", + text: "Drafting your update.", + createdAt: "2026-04-11T10:04:00.000Z", + }, + ], + }); + let capturedProps: Record | null = null; + + const { ShellBallBubbleWindow: RuntimeShellBallBubbleWindow } = withShellBallModuleRuntime("ShellBallBubbleWindow.tsx", { + react: require("react"), + "./useShellBallCoordinator": { + useShellBallHelperWindowSnapshot() { + return helperSnapshot; + }, + }, + "./useShellBallWindowMetrics": { + useShellBallWindowMetrics() { + return { rootRef: null }; + }, + }, + "./components/ShellBallBubbleZone": { + ShellBallBubbleZone(props: Record) { + capturedProps = props; + return createElement("section", { className: "shell-ball-bubble-zone-stub" }); + }, + }, + }, (moduleExports) => moduleExports as { ShellBallBubbleWindow: typeof import("./ShellBallBubbleWindow").ShellBallBubbleWindow }); + + renderToStaticMarkup(createElement(RuntimeShellBallBubbleWindow, null)); + + assert.deepEqual(capturedProps, { + visualState: "processing", + bubbleMessages: helperSnapshot.bubbleMessages, + }); +}); + +test("shell-ball bubble window does not depend on only visualState to render its body", () => { + const helperSnapshot = createShellBallWindowSnapshot({ + visualState: "idle", + inputValue: "", + voicePreview: null, + bubbleMessages: [ + { + id: "msg-helper-2", + role: "user", + text: "Open the dashboard.", + createdAt: "2026-04-11T10:05:00.000Z", + }, + ], + }); + let capturedProps: Record | null = null; + + const { ShellBallBubbleWindow: RuntimeShellBallBubbleWindow } = withShellBallModuleRuntime("ShellBallBubbleWindow.tsx", { + react: require("react"), + "./useShellBallCoordinator": { + useShellBallHelperWindowSnapshot() { + return helperSnapshot; + }, + }, + "./useShellBallWindowMetrics": { + useShellBallWindowMetrics() { + return { rootRef: null }; + }, + }, + "./components/ShellBallBubbleZone": { + ShellBallBubbleZone(props: Record) { + capturedProps = props; + return createElement("section", { className: "shell-ball-bubble-zone-stub" }); + }, + }, + }, (moduleExports) => moduleExports as { ShellBallBubbleWindow: typeof import("./ShellBallBubbleWindow").ShellBallBubbleWindow }); + + renderToStaticMarkup(createElement(RuntimeShellBallBubbleWindow, { visualState: "voice_locked" })); + + assert.deepEqual(capturedProps, { + visualState: "voice_locked", + bubbleMessages: helperSnapshot.bubbleMessages, + }); +}); + test("shell-ball input window owns the input rendering", () => { const markup = renderToStaticMarkup( createElement(ShellBallInputWindow, { diff --git a/apps/desktop/src/features/shell-ball/useShellBallCoordinator.ts b/apps/desktop/src/features/shell-ball/useShellBallCoordinator.ts index c6eea7c78..d71f43acf 100644 --- a/apps/desktop/src/features/shell-ball/useShellBallCoordinator.ts +++ b/apps/desktop/src/features/shell-ball/useShellBallCoordinator.ts @@ -1,6 +1,7 @@ import { useEffect, useMemo, useRef, useState } from "react"; import { getCurrentWindow } from "@tauri-apps/api/window"; import { shellBallWindowLabels } from "../../platform/shellBallWindowController"; +import type { ShellBallBubbleMessage } from "./shellBall.bubble"; import type { ShellBallVoicePreview } from "./shellBall.interaction"; import type { ShellBallInputBarMode, ShellBallVisualState } from "./shellBall.types"; import { @@ -33,6 +34,21 @@ type ShellBallHelperSnapshotInput = { role: ShellBallHelperWindowRole; }; +const SHELL_BALL_LOCAL_BUBBLE_MESSAGES: ShellBallBubbleMessage[] = [ + { + id: "shell-ball-local-agent-1", + role: "agent", + text: "Drafting your update.", + createdAt: "2026-04-11T10:04:00.000Z", + }, + { + id: "shell-ball-local-user-1", + role: "user", + text: "Open the dashboard.", + createdAt: "2026-04-11T10:05:00.000Z", + }, +]; + export function useShellBallCoordinator(input: ShellBallCoordinatorInput) { const snapshot = useMemo( () => @@ -40,6 +56,7 @@ export function useShellBallCoordinator(input: ShellBallCoordinatorInput) { visualState: input.visualState, inputValue: input.inputValue, voicePreview: input.voicePreview, + bubbleMessages: SHELL_BALL_LOCAL_BUBBLE_MESSAGES, }), [input.inputValue, input.visualState, input.voicePreview], ); From 927971b28bb3cea394ee3e02847660e4bb652f83 Mon Sep 17 00:00:00 2001 From: gdemonc <1502689916@qq.com> Date: Sat, 11 Apr 2026 17:10:28 +0800 Subject: [PATCH 05/19] feat(desktop-shell-ball): render bubble message list --- .../components/ShellBallBubbleMessage.tsx | 18 +++++++++++ .../components/ShellBallBubbleZone.tsx | 12 ++++--- .../shell-ball/shellBall.contract.test.ts | 32 +++++++++++++++++++ 3 files changed, 58 insertions(+), 4 deletions(-) create mode 100644 apps/desktop/src/features/shell-ball/components/ShellBallBubbleMessage.tsx diff --git a/apps/desktop/src/features/shell-ball/components/ShellBallBubbleMessage.tsx b/apps/desktop/src/features/shell-ball/components/ShellBallBubbleMessage.tsx new file mode 100644 index 000000000..1f6bf81f2 --- /dev/null +++ b/apps/desktop/src/features/shell-ball/components/ShellBallBubbleMessage.tsx @@ -0,0 +1,18 @@ +import type { ShellBallBubbleMessage as ShellBallBubbleMessageModel } from "../shellBall.bubble"; + +type ShellBallBubbleMessageProps = { + message: ShellBallBubbleMessageModel; +}; + +export function ShellBallBubbleMessage({ message }: ShellBallBubbleMessageProps) { + return ( +
+
+

{message.text}

+
+
+ ); +} diff --git a/apps/desktop/src/features/shell-ball/components/ShellBallBubbleZone.tsx b/apps/desktop/src/features/shell-ball/components/ShellBallBubbleZone.tsx index c40e9ca22..109378736 100644 --- a/apps/desktop/src/features/shell-ball/components/ShellBallBubbleZone.tsx +++ b/apps/desktop/src/features/shell-ball/components/ShellBallBubbleZone.tsx @@ -1,16 +1,20 @@ import type { ShellBallBubbleMessage } from "../shellBall.bubble"; import type { ShellBallVisualState } from "../shellBall.types"; +import { ShellBallBubbleMessage as ShellBallBubbleMessageView } from "./ShellBallBubbleMessage"; type ShellBallBubbleZoneProps = { visualState: ShellBallVisualState; bubbleMessages?: ShellBallBubbleMessage[]; }; -export function ShellBallBubbleZone({ visualState }: ShellBallBubbleZoneProps) { +export function ShellBallBubbleZone({ visualState, bubbleMessages = [] }: ShellBallBubbleZoneProps) { return ( -