feat(FN-3806): implement rooms create modal and chat sidebar with plugin ro

Adds a rooms feature to the chat sidebar including a new CreateRoomModal for creating chat rooms, with mobile selection handling and search empty-state copy fixes. Also introduces listTasksModifiedSince plugin contract and report plugin scaffold, replacing the removed dashboard roadmap backend.

Fusion-Task-Id: FN-3806
This commit is contained in:
Fusion
2026-05-09 02:07:07 -07:00
committed by gsxdsm
parent 94a6fe41ef
commit dd295bdcdc
6 changed files with 694 additions and 3 deletions

View File

@@ -33,6 +33,7 @@ import { CustomModelDropdown } from "./CustomModelDropdown";
import { ProviderIcon } from "./ProviderIcon";
import { AgentMentionPopup } from "./AgentMentionPopup";
import { FileMentionPopup } from "./FileMentionPopup";
import { CreateRoomModal, type RoomDraft } from "./CreateRoomModal";
import { useFileMention } from "../hooks/useFileMention";
import { useMobileKeyboard } from "../hooks/useMobileKeyboard";
import { useMobileScrollLock } from "../hooks/useMobileScrollLock";
@@ -740,6 +741,10 @@ export function ChatView({ projectId, addToast }: ChatViewProps) {
* add room data models, APIs, and routing.
*/
const [chatScope, setChatScope] = useState<"direct" | "rooms">("direct");
const [createRoomOpen, setCreateRoomOpen] = useState(false);
// FN-3807: replace draftRooms with backend-backed state.
const [draftRooms, setDraftRooms] = useState<RoomDraft[]>([]);
const [activeDraftRoomName, setActiveDraftRoomName] = useState<string | null>(null);
const [agentsMap, setAgentsMap] = useState<Map<string, Agent>>(new Map());
const [discoveredSkills, setDiscoveredSkills] = useState<DiscoveredSkill[]>([]);
const [skillsLoading, setSkillsLoading] = useState(true);
@@ -1722,7 +1727,7 @@ export function ChatView({ projectId, addToast }: ChatViewProps) {
{chatScope === "direct" ? (
<>
{/* Search section */}
<div className="chat-sidebar-search">
<div className="chat-sidebar-search-section">
<div className="chat-sidebar-search-wrapper">
<Search size={14} className="chat-sidebar-search-icon" />
<input
@@ -1792,8 +1797,47 @@ export function ChatView({ projectId, addToast }: ChatViewProps) {
</div>
</>
) : (
<div className="chat-sidebar-rooms-empty" data-testid="chat-sidebar-rooms-empty">
No rooms yet room creation lands in a follow-up task.
<div className="chat-sidebar-rooms" data-testid="chat-sidebar-rooms">
<div className="chat-sidebar-rooms-header">
<button
type="button"
className="btn btn-sm btn-primary"
data-testid="chat-create-room-btn"
onClick={() => setCreateRoomOpen(true)}
>
<Plus size={14} />
Create room
</button>
</div>
{draftRooms.length === 0 ? (
<div className="chat-sidebar-rooms-empty" data-testid="chat-sidebar-rooms-empty">
No rooms yet.
</div>
) : (
<div className="chat-session-list chat-sidebar-list">
{draftRooms.map((room) => {
const memberCount = room.memberAgentIds.length;
const isActive = activeDraftRoomName === room.name;
return (
<button
key={room.name}
type="button"
className={`chat-room-item${isActive ? " chat-room-item--active" : ""}`}
data-testid={`chat-room-item-${room.name}`}
onClick={() => {
setActiveDraftRoomName(room.name);
if (isMobile) {
setSidebarVisible(false);
}
}}
>
<span className="chat-room-item-name">{room.displayName}</span>
<span className="chat-room-item-meta">{memberCount} member{memberCount === 1 ? "" : "s"}</span>
</button>
);
})}
</div>
)}
</div>
)}
{/* Mobile footer with New Chat action */}
@@ -1875,6 +1919,16 @@ export function ChatView({ projectId, addToast }: ChatViewProps) {
)}
{/* Thread */}
{chatScope === "rooms" ? (
<div className="chat-thread chat-thread--rooms-placeholder" data-testid="chat-rooms-placeholder-pane">
<div className="chat-rooms-placeholder-title">
{activeDraftRoomName ? `#${activeDraftRoomName}` : "Select a room"}
</div>
<div className="chat-rooms-placeholder-copy">
Coming soon room messaging is being wired up (FN-3807).
</div>
</div>
) : (
<div
className={`chat-thread${keyboardOpen && hasKeyboardViewportDisplacement ? " chat-thread--keyboard-active" : ""}`}
style={threadKeyboardStyle}
@@ -2201,6 +2255,19 @@ export function ChatView({ projectId, addToast }: ChatViewProps) {
</div>
)}
</div>
)}
<CreateRoomModal
isOpen={createRoomOpen}
onClose={() => setCreateRoomOpen(false)}
projectId={projectId}
existingRoomNames={draftRooms.map((room) => room.name)}
onCreate={(draft) => {
setDraftRooms((prev) => [...prev, draft]);
setActiveDraftRoomName(draft.name);
setCreateRoomOpen(false);
}}
/>
{/* New Chat Dialog (rendered at root level) */}
{showNewDialog && (