ELNOR REPO READER TEXT MIRROR Original path: Design Mockups/Archived Mockups/DOC20 Mockups and Design/DOC20 Archived Mockups/Q_UNIFIED_WORKSPACE_V2.jsx Source repo: /Users/OpenClaw1/Elnor/Elnor Specs Git branch: main Git commit: dbaa25962edc11ab30e8d4ca1715f9ae5bf77331 Generated: 2026-06-09T01:23:58.539Z --- import { useState, useRef, useEffect, useMemo, useCallback } from "react"; const font={sans:"'Söhne','Helvetica Neue',-apple-system,BlinkMacSystemFont,sans-serif"}; const R={sm:"6px",md:"10px",full:"9999px"}; const c={bgApp:"#F8F8F6",bgPanel:"#FFFFFF",bgPanelAlt:"#F9FAFB",bgCard:"#FFFFFF",bgInput:"#EFF1F3",bgSidebar:"#131820",textPri:"#1A1D21",textSec:"#5E6570",textTer:"#8B919A",accentBtn:"#31588c",warn:"#D97706",error:"#B04040",border:"#E0E2E5",borderLight:"#ECEEF0",green:"#2E8B57",neutral:"#6B7280",agentAv:"#a1a7aa",borderDark:"#263040",purple:"#7C3AED"}; const Ic=({d,size=18,color,sw=1.75})=>; const I={Search:p=>,Plus:p=>,ChevR:p=>,ChevD:p=>,Pin:p=>,Folder:p=>,File:p=>,X:p=>,Check:p=>,Save:p=>,Trash:p=>,Copy:p=>,Link:p=>,Maximize:p=>,Spark:p=>,MsgCircle:p=>,Undo:p=>,Redo:p=>,Eye:p=>,Edit:p=>,FileText:p=>,ExtLink:p=>,Printer:p=>,Clock:p=>,ArrowL:p=>,ArrowR:p=>,Refresh:p=>,Star:p=>,Lock:p=>,Book:p=>,Scissors:p=>,Globe:p=>,Home:p=>,Settings:p=>,Inbox:p=>}; const Dot=({color,size=8})=>; const Toast=({msg,onDone})=>{useEffect(()=>{const t=setTimeout(onDone,2200);return()=>clearTimeout(t)},[onDone]);return
{msg}
}; const TBtn=({icon,title,active,onClick,label,dropdown})=>; const Sep=()=>
; const Btn=({children,primary,ghost,small,onClick,disabled,style:s})=>; const Av=({letter,color,size=20})=>
{letter}
; const DItem=({children,onClick,active})=>; const Radio=({selected,label,desc,onClick})=>; // ═══ DATA ═══ const agentName="Elnor"; const agents=[{name:"Elnor",color:c.agentAv,letter:"E"},{name:"Scout",color:"#5B5F97",letter:"S"}]; const typeColors={Note:c.accentBtn,Chat:c.green,Doc:c.textTer,Task:c.warn,Artifact:"#8B5CF6",Prompt:"#D97706",Preset:c.neutral,Skill:c.neutral,Agent:"#5B5F97"}; const projects=[{id:1,name:"Paramount v. City of LA",color:"#31588c"},{id:2,name:"White v. Brooge Energy",color:"#2E8B57"},{id:3,name:"Internal Ops",color:"#8B7355"}]; const collections=[{name:"Priority",color:"#B04040"},{name:"Research",color:"#2E8B57"},{name:"Draft",color:"#D97706"},{name:"Archive",color:"#6B7280"},{name:"Urgent",color:"#9E5E5E"},{name:"Reference",color:"#5B5F97"}]; const scopeFams=["Collection","Project","Bucket","Places","Folders","Saved Views","Notes"]; const typeChips=["Document","Chat","Preset","Skill","Task","Agent","Overlay","Note","Artifact","Prompt"]; const sortOptions=["Modified","Alphabetical","Type","Created","Running"]; const initFolders=[{id:"f1",title:"Paramount Case",parent:null},{id:"f2",title:"Expert Reports",parent:"f1"},{id:"f3",title:"Motion Practice",parent:"f1"},{id:"f4",title:"Brooge Research",parent:null},{id:"f5",title:"Reference Materials",parent:null}]; const noteFolders=[{id:"nf1",title:"Case Notes",parent:null},{id:"nf2",title:"Paramount",parent:"nf1"},{id:"nf3",title:"Brooge",parent:"nf1"},{id:"nf4",title:"Session Notes",parent:null}]; const savedViews=[{id:"sv1",name:"Current",sys:true},{id:"sv2",name:"Recent",sys:true},{id:"sv3",name:"No Project",sys:true}]; const allItems=[ {type:"Note",title:"Today — April 4, 2026",time:"2h",pin:true,proj:1,cols:["Priority"]}, {type:"Note",title:"Trial Prep Checklist",time:"4h",pin:true,proj:1,cols:[]}, {type:"Doc",title:"Sanli Expert Report.pdf",time:"10m",proj:1,cols:["Priority"]}, {type:"Artifact",title:"Motion in Limine No. 3",time:"1d",proj:1,cols:[]}, {type:"Doc",title:"Christensen_CV.pdf",time:"1d",proj:1,cols:["Reference"]}, {type:"Note",title:"Brooge Loss Causation Analysis",time:"1d",proj:2,cols:["Research"]}, {type:"Task",title:"Review Sanli depo transcript",time:"2h",proj:1,st:"running",cols:[]}, {type:"Chat",title:"Paramount damages strategy",time:"1h",proj:1,cols:["Priority"]}, {type:"Artifact",title:"Fair Fund Analysis — Brooge",time:"2h",proj:2,cols:[]}, {type:"Doc",title:"BC587659_MIL_3.pdf",time:"2d",proj:1,cols:[]}, {type:"Note",title:"Danny Christensen Engagement",time:"3d",proj:1,cols:[]}, {type:"Task",title:"Draft Paramount trial brief §III",time:"4h",proj:1,st:"waiting",cols:["Priority"]}, {type:"Prompt",title:"Expert Analysis Template",time:"1w",proj:null,cols:[]}, {type:"Preset",title:"Legal Brief Review Preset",time:"1w",proj:null,cols:[]}, {type:"Doc",title:"Standing_Orders.md",time:"3d",proj:null,cols:["Reference"]}, ]; const timeVal=t=>{if(t.includes("m"))return parseInt(t);if(t.includes("h"))return parseInt(t)*60;if(t.includes("d"))return parseInt(t)*1440;if(t.includes("w"))return parseInt(t)*10080;return 99999}; // Tab definitions const tabColors={note:"#31588c",doc:"#2E8B57",web:"#D97706",clips:"#7C3AED"}; const initTabs=[ {id:"t1",type:"note",icon:"📝",title:"Today — April 4, 2026",color:tabColors.note}, {id:"t2",type:"doc",icon:"📄",title:"Sanli Expert Report.pdf",color:tabColors.doc}, {id:"t3",type:"web",icon:"🌐",title:"Paramount MIL — PACER",color:tabColors.web}, {id:"t4",type:"clips",icon:"✂️",title:"Clips: 4.4-1",color:tabColors.clips}, ]; // Comments (shared across tab types for demo) const initComments=[ {id:"c1",author:"You",color:c.accentBtn,body:"Verify Sanli's discount rate — 18% seems unsupported.",time:"10m ago",status:"open",anchor:"discount rate of 18%",replies:[{id:"r1",author:agentName,color:c.agentAv,body:"Found 3 comparable transactions: average 8-12%. Sanli's 18% is an outlier.",time:"5m ago"}]}, {id:"c2",author:"You",color:c.accentBtn,body:"Cross-ref with Christensen's methodology.",time:"8m ago",status:"open",anchor:"willing buyer-willing seller",replies:[]}, {id:"c3",author:agentName,color:c.agentAv,body:"Sargon gatekeeper standard fully briefed in §II.",time:"6m ago",status:"resolved",anchor:"Legal Standard",replies:[]}, ]; // ═══ MAIN COMPONENT ═══ export default function UnifiedWorkspaceV2(){ // Tab state const [tabs,setTabs]=useState(initTabs); const [activeTabId,setActiveTabId]=useState("t1"); const [tabContextMenu,setTabContextMenu]=useState(null); const [newTabDrop,setNewTabDrop]=useState(false); // Browser column state const [browserOpen,setBrowserOpen]=useState(true); const [browserWidth,setBrowserWidth]=useState(280); const [activeScope,setActiveScope]=useState(null); const [activeProjIdx,setActiveProjIdx]=useState(0);const [noProject,setNoProject]=useState(false); const [activeTypes,setActiveTypes]=useState(new Set()); const [selectedCollections,setSelectedCollections]=useState(new Set()); const [splitterPos,setSplitterPos]=useState(110); const [selectedIdx,setSelectedIdx]=useState(null); const [folders,setFolders]=useState(initFolders); const [expandedFolders,setExpandedFolders]=useState(new Set(["f1"])); const [activeFolder,setActiveFolder]=useState(null); const [searchMode,setSearchMode]=useState("this_view"); const [sortKey,setSortKey]=useState("Modified");const [sortDrop,setSortDrop]=useState(false); const [folderOverlay,setFolderOverlay]=useState(false); const [dropTarget,setDropTarget]=useState(null);const [dragItem,setDragItem]=useState(null); const [newFolderMode,setNewFolderMode]=useState(null);const [newFolderName,setNewFolderName]=useState(""); const [hoverFolder,setHoverFolder]=useState(null); const [activeSavedView,setActiveSavedView]=useState(null); const [userSavedViews,setUserSavedViews]=useState([]); // Note folder expansion for Notes scope const [noteExpandedFolders,setNoteExpandedFolders]=useState(new Set(["nf1"])); // Right panel state const [rightOpen,setRightOpen]=useState(false);const [rightTab,setRightTab]=useState("comments"); const [rightWidth,setRightWidth]=useState(280); // Comments state const [comments,setComments]=useState(initComments); const [activeComment,setActiveComment]=useState(null); const [replyingTo,setReplyingTo]=useState(null);const [replyText,setReplyText]=useState(""); const [newCm,setNewCm]=useState(null);const [newCmText,setNewCmText]=useState(""); // Ask Agent state const [agentIdx,setAgentIdx]=useState(0);const [agentDrop,setAgentDrop]=useState(false); const [instruction,setInstruction]=useState(""); const [includeItems,setIncludeItems]=useState([ {id:"i1",label:"Current content + comments",checked:true}, {id:"i2",label:"Paramount project context",checked:true}, {id:"i3",label:"Session clips (2 items)",checked:false}, {id:"i4",label:"Pinned pages",checked:false}, ]); // Content-specific state const [showMarkup,setShowMarkup]=useState(true); const [findBar,setFindBar]=useState(false); const [pinned,setPinned]=useState(false); const [bookmarked,setBookmarked]=useState(false); const [readerMode,setReaderMode]=useState(false); const [openDrop,setOpenDrop]=useState(null); const [bubbleMenu,setBubbleMenu]=useState(null); // Session state const [sessionMinutes,setSessionMinutes]=useState(47); const [clipsCount,setClipsCount]=useState(2); const [toast,setToast]=useState(null); const editorRef=useRef(null);const cmRef=useRef(null);const nfRef=useRef(null); const flash=msg=>setToast(msg); const agent=agents[agentIdx]; const activeTab=tabs.find(t=>t.id===activeTabId)||tabs[0]; const openC=comments.filter(x=>x.status==="open"); const resolvedC=comments.filter(x=>x.status==="resolved"); // Session timer useEffect(()=>{const iv=setInterval(()=>setSessionMinutes(m=>m+1),60000);return()=>clearInterval(iv)},[]); // Context-sensitive browser scope auto-switching useEffect(()=>{ if(activeTab.type==="note"&&activeScope!=="Notes")setActiveScope("Notes"); },[activeTabId]); // Browser filtering const toggleType=t=>{const s=new Set(activeTypes);s.has(t)?s.delete(t):s.add(t);setActiveTypes(s)}; const toggleCollection=name=>{const s=new Set(selectedCollections);s.has(name)?s.delete(name):s.add(name);setSelectedCollections(s)}; const toggleFolder=id=>{const s=new Set(expandedFolders);s.has(id)?s.delete(id):s.add(id);setExpandedFolders(s)}; const selectScope=s=>{if(activeScope===s){setActiveScope(null);setActiveFolder(null);setNoProject(false);setActiveSavedView(null)}else{setActiveScope(s);setActiveFolder(null);setNoProject(false);setActiveSavedView(null)}}; const createFolder=parent=>{if(!newFolderName.trim())return;setFolders(p=>[...p,{id:"f"+(p.length+10),title:newFolderName.trim(),parent}]);if(parent){const s=new Set(expandedFolders);s.add(parent);setExpandedFolders(s)}setNewFolderMode(null);setNewFolderName("");flash("Folder created")}; const filtered=useMemo(()=>{ let items=[...allItems]; if(activeScope==="Project"&&!noProject)items=items.filter(it=>it.proj===projects[activeProjIdx].id); if(activeScope==="Project"&&noProject)items=items.filter(it=>it.proj===null); if(activeScope==="Notes")items=items.filter(it=>it.type==="Note"); if(selectedCollections.size>0)items=items.filter(it=>[...selectedCollections].every(col=>it.cols?.includes(col))); if(activeTypes.size>0)items=items.filter(it=>activeTypes.has(it.type)); if(sortKey==="Alphabetical")items.sort((a,b)=>a.title.localeCompare(b.title)); else if(sortKey==="Type")items.sort((a,b)=>a.type.localeCompare(b.type)); else if(sortKey==="Running")items.sort((a,b)=>(b.st==="running"?1:0)-(a.st==="running"?1:0)); else items.sort((a,b)=>timeVal(a.time)-timeVal(b.time)); const pp=items.filter(x=>x.pin);const up=items.filter(x=>!x.pin); return [...pp,...up]; },[activeScope,activeProjIdx,noProject,selectedCollections,activeTypes,sortKey]); // Folder tree renderer const renderFolderTree=(parentId,depth,isOverlay=false,folderList=folders)=>folderList.filter(f=>f.parent===parentId).map(f=>{ const hasChildren=folderList.some(ch=>ch.parent===f.id);const isExp=expandedFolders.has(f.id); const isActive=!isOverlay&&activeFolder===f.id;const isDrop=dropTarget===f.id;const isHover=hoverFolder===f.id; return
{if(isOverlay)return;setActiveFolder(isActive?null:f.id)}} onMouseEnter={()=>setHoverFolder(f.id)} onMouseLeave={()=>setHoverFolder(null)} onDragOver={e=>{e.preventDefault();setDropTarget(f.id)}} onDragLeave={()=>setDropTarget(null)} onDrop={e=>{e.preventDefault();setDropTarget(null);if(dragItem)flash(`Added "${dragItem}" to ${f.title}`)}} style={{display:"flex",alignItems:"center",gap:3,padding:isOverlay?"2px 6px":"3px 8px",paddingLeft:(isOverlay?6:8)+depth*(isOverlay?12:14),borderRadius:R.sm,cursor:isOverlay?"default":"pointer",backgroundColor:isDrop?c.accentBtn+"20":isActive?c.accentBtn+"10":"transparent",fontSize:isOverlay?10.5:11.5,fontWeight:isActive?600:400,color:isActive?c.accentBtn:c.textSec,border:isDrop?`1px dashed ${c.accentBtn}`:"1px solid transparent",transition:"all .1s"}}> {hasChildren?{e.stopPropagation();toggleFolder(f.id)}} style={{display:"flex",width:10}}>{isExp?:}:} {f.title} {isHover&&!isOverlay&&{e.stopPropagation();setNewFolderMode({parent:f.id});setNewFolderName("");setTimeout(()=>nfRef.current?.focus(),50)}} style={{cursor:"pointer",display:"flex",color:c.textTer}} title="Add subfolder">}
{isExp&&hasChildren&&renderFolderTree(f.id,depth+1,isOverlay,folderList)} {!isOverlay&&newFolderMode&&newFolderMode.parent===f.id&&
setNewFolderName(e.target.value)} placeholder="Subfolder…" onKeyDown={e=>{if(e.key==="Enter")createFolder(f.id);if(e.key==="Escape"){setNewFolderMode(null);setNewFolderName("")}}} style={{flex:1,border:`1px solid ${c.accentBtn}40`,borderRadius:3,padding:"2px 6px",fontSize:11,fontFamily:font.sans,outline:"none",backgroundColor:c.bgCard}}/>
}
; }); // Note folder tree for Notes scope const renderNoteFolders=(parentId,depth)=>noteFolders.filter(f=>f.parent===parentId).map(f=>{ const hasChildren=noteFolders.some(ch=>ch.parent===f.id);const isExp=noteExpandedFolders.has(f.id); return
{const s=new Set(noteExpandedFolders);s.has(f.id)?s.delete(f.id):s.add(f.id);setNoteExpandedFolders(s)}} style={{display:"flex",alignItems:"center",gap:3,padding:"3px 8px",paddingLeft:8+depth*14,borderRadius:R.sm,cursor:"pointer",fontSize:11.5,color:c.textSec}}> {hasChildren?{isExp?:}:} {f.title}
{isExp&&hasChildren&&renderNoteFolders(f.id,depth+1)}
; }); // Bubble menu handler const handleMouseUp=useCallback(e=>{if(e.target.closest("[data-bubble]"))return;const sel=window.getSelection();if(!sel||sel.isCollapsed||!sel.toString().trim()){setBubbleMenu(null);return}const text=sel.toString().trim();if(text.length<3)return;const rect=sel.getRangeAt(0).getBoundingClientRect();const er=editorRef.current?.getBoundingClientRect();if(!er)return;setBubbleMenu({x:rect.left-er.left+rect.width/2,y:rect.top-er.top-8,text})},[]); const dismissBubble=()=>{setBubbleMenu(null);window.getSelection()?.removeAllRanges()}; // Comment actions const addReply=cmId=>{if(!replyText.trim())return;setComments(p=>p.map(cm=>cm.id===cmId?{...cm,replies:[...cm.replies,{id:"r"+Date.now(),author:"You",color:c.accentBtn,body:replyText.trim(),time:"just now"}]}:cm));setReplyingTo(null);setReplyText("")}; const resolveComment=id=>setComments(p=>p.map(cm=>cm.id===id?{...cm,status:"resolved"}:cm)); const reopenComment=id=>setComments(p=>p.map(cm=>cm.id===id?{...cm,status:"open"}:cm)); const addComment=()=>{if(!newCmText.trim())return;setComments(p=>[...p,{id:"c"+Date.now(),author:"You",color:c.accentBtn,body:newCmText.trim(),time:"just now",status:"open",anchor:newCm?.text||null,replies:[]}]);setNewCm(null);setNewCmText("")}; // Tab actions const closeTab=id=>{if(tabs.length<=1)return;const idx=tabs.findIndex(t=>t.id===id);setTabs(p=>p.filter(t=>t.id!==id));if(activeTabId===id){const next=tabs[idx===0?1:idx-1];if(next)setActiveTabId(next.id)}}; const addTab=(type)=>{const id="t"+Date.now();const newTab=type==="note"?{id,type:"note",icon:"📝",title:"Untitled Note",color:tabColors.note}:type==="doc"?{id,type:"doc",icon:"📄",title:"Open Document…",color:tabColors.doc}:{id,type:"web",icon:"🌐",title:"New Tab",color:tabColors.web};setTabs(p=>[...p,newTab]);setActiveTabId(id);setNewTabDrop(false);flash(type==="note"?"New note":"New tab")}; // Comment card component const CmCard=({cm,resolved})=>{const isAct=activeComment===cm.id;return
setActiveComment(isAct?null:cm.id)}>
{cm.anchor&&
"{cm.anchor.slice(0,55)}"
}
{cm.author}{cm.time}
{cm.body}
e.stopPropagation()}> {!resolved&&{setReplyingTo(replyingTo===cm.id?null:cm.id);setReplyText("")}}>Reply} {!resolved&&resolveComment(cm.id)}>Resolve} {resolved&&reopenComment(cm.id)}>Reopen} {cm.author==="You"&&Edit} {cm.author==="You"&&Delete} flash(`Sent to ${agent.name}`)} style={{cursor:"pointer",display:"flex",alignItems:"center",gap:2,color:c.textSec,fontWeight:600,fontSize:10}} onMouseEnter={e=>e.currentTarget.style.color=c.accentBtn} onMouseLeave={e=>e.currentTarget.style.color=c.textSec}>Send
{cm.replies.map(r=>
{r.author}{r.time}
{r.body}
)} {replyingTo===cm.id&&
e.stopPropagation()}>