- Extract redeliver_missed(tx, db, log) helper into cluster_transport.rs - heal_region now removes partition then immediately ships any missed batch-log entries to the healed follower's channel - await_convergence refactored to call the same helper (no logic change) - tidal-server: reload_text_index before search in cluster mode - tidal-server: write_signal returns Result instead of panicking on unknown signal - tidal-server: leader shows lag_events=0 (writes directly, no receiver thread) - tidal-server: fix cluster mode error propagation (ServerError::from) - docs/runbooks/cluster.md: add full cluster operations runbook - docker/: add Dockerfile for containerised cluster deployment - README.md: add tidal-server HTTP API getting-started section - Split oversized source files per CODING_GUIDELINES §9 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
57 lines
1.5 KiB
TypeScript
57 lines
1.5 KiB
TypeScript
"use client";
|
|
|
|
import { useEffect, useRef } from "react";
|
|
import { MessageList } from "./message-list";
|
|
import { InputBar } from "./input-bar";
|
|
import { useChatStore } from "@/lib/store";
|
|
import type { ChatMessage } from "@/lib/types";
|
|
|
|
export function ChatContainer() {
|
|
const error = useChatStore((s) => s.error);
|
|
const activeId = useChatStore((s) => s.activeConversationId);
|
|
const setMessages = useChatStore((s) => s.setMessages);
|
|
const prevIdRef = useRef<string | null>(null);
|
|
|
|
// Load messages from Synap when switching conversations
|
|
useEffect(() => {
|
|
if (!activeId || activeId === prevIdRef.current) return;
|
|
prevIdRef.current = activeId;
|
|
|
|
let cancelled = false;
|
|
|
|
async function load() {
|
|
try {
|
|
const res = await fetch(`/api/conversations/${activeId}/messages`);
|
|
if (!res.ok || cancelled) return;
|
|
const data: { messages: ChatMessage[] } = await res.json();
|
|
if (!cancelled && data.messages.length > 0) {
|
|
setMessages(data.messages);
|
|
}
|
|
} catch {
|
|
// Silently fail — empty conversation is fine
|
|
}
|
|
}
|
|
|
|
load();
|
|
return () => {
|
|
cancelled = true;
|
|
};
|
|
}, [activeId, setMessages]);
|
|
|
|
return (
|
|
<div className="flex flex-col flex-1 min-w-0 h-dvh">
|
|
<MessageList />
|
|
|
|
{error && (
|
|
<div className="px-4 md:px-8">
|
|
<div className="max-w-2xl mx-auto">
|
|
<p className="text-negative text-sm py-2">{error}</p>
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
<InputBar />
|
|
</div>
|
|
);
|
|
}
|