ELNOR REPO READER TEXT MIRROR Original path: Design Mockups/Archived Mockups/DOC20 Mockups and Design/DOC20 Archived Mockups/DOC20 Workspace Redo/Q_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, useCallback, useEffect, useMemo } from "react"; // ─── DESIGN TOKENS (exact match to Q mockup suite) ─── 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" }; // ─── ICONS ─── const Ic = ({ d, d2, size = 18, color, sw = 1.75 }) => ( {d2 && } ); const I = { Plus: p => , X: p => , Check: p => , ChevD: p => , ChevR: p => , ChevL: p => , Search: p => , Spark: p => , MsgCircle: p => , Pin: p => , FileText: p => , Folder: p => , File: p => , Save: p => , Copy: p => , Link: p => , Eye: p => , Edit: p => , Undo: p => , Redo: p => , Maximize: p => , Trash: p => , Archive: p => , Settings: p => , Bell: p => , Calendar: p => , Mail: p => , Zap: p => , List: p => , Sun: p => , }; // ─── SHARED COMPONENTS ─── const Dot = ({ color, size = 8 }) => ; const Btn = ({ children, primary, ghost, small, onClick, style: s }) => ( ); const Av = ({ letter, color, size = 20 }) =>
{letter}
; const TBtn = ({ icon, title, active, onClick, label, dropdown }) => ( ); const Sep = () =>
; // ─── BROWSER DATA ─── const typeColors = { Note: c.accentBtn, Chat: c.green, Doc: c.textTer, Task: c.warn, Artifact: "#8B5CF6", Prompt: "#D97706" }; const projects = [{ id: 1, name: "Henderson v. DataCorp", color: "#31588c" }, { id: 2, name: "Quark Research", color: "#2E8B57" }]; const collections = [{ name: "Priority", color: "#B04040" }, { name: "Research", color: "#2E8B57" }, { name: "Draft", color: "#D97706" }, { name: "Archive", color: "#6B7280" }]; const scopeChips = ["Collection", "Project", "Bucket", "Places", "Folders", "Saved Views"]; const typeChips = ["Document", "Chat", "Preset", "Skill", "Task", "Agent", "Overlay", "Note", "Artifact", "Prompt"]; const sortOptions = ["Modified", "Alphabetical", "Type", "Created", "Last Used"]; const browserItems = [ { type: "Note", title: "Henderson MSJ — Work Notes", time: "2h", pin: true, proj: 1, cols: ["Priority"] }, { type: "Note", title: "Weekly Task List", time: "4h", pin: true, proj: 1 }, { type: "Chat", title: "Henderson discovery analysis", time: "2m", unread: true, proj: 1 }, { type: "Artifact", title: "MSJ Response Brief v3", time: "10m", proj: 1 }, { type: "Doc", title: "Henderson_Complaint.pdf", time: "1d", proj: 1 }, { type: "Note", title: "Quark Patent Analysis", time: "1d", proj: 2, cols: ["Research"] }, { type: "Task", title: "Review batch #4", time: "2h", proj: 1, st: "running" }, { type: "Chat", title: "Quark patent search", time: "1h", proj: 2 }, { type: "Artifact", title: "Henderson Timeline Analysis", time: "2h", proj: 1 }, { type: "Doc", title: "Patent_US20240001.pdf", time: "2d", proj: 2 }, { type: "Note", title: "Judge Chen Conference Notes", time: "3d", proj: 1 }, { type: "Task", title: "Draft motion response", time: "4h", proj: 1, st: "waiting" }, { type: "Artifact", title: "Privilege Log Summary v3", time: "3d", proj: 1 }, { type: "Prompt", title: "Discovery Review Template", time: "1w" }, ]; // ─── NOTES SIDEBAR DATA ─── const noteFolders = [ { id: "nf1", title: "Henderson Case", parent: null }, { id: "nf2", title: "Privilege Issues", parent: "nf1" }, { id: "nf3", title: "Motion Practice", parent: "nf1" }, { id: "nf4", title: "Quark Patents", parent: null }, ]; const noteList = [ { id: "n1", title: "Henderson MSJ — Work Notes", folder: "nf3", mod: "2h", comments: 3, pinned: true, pending: 2, proj: "Henderson", projColor: c.accentBtn }, { id: "n2", title: "Weekly Task List", folder: null, mod: "4h", comments: 0, pinned: true, proj: "Henderson", projColor: c.accentBtn }, { id: "n3", title: "Judge Chen Conference Notes", folder: "nf3", mod: "3d", comments: 2, proj: "Henderson", projColor: c.accentBtn }, { id: "n4", title: "Quark Patent Analysis", folder: "nf4", mod: "1d", comments: 1, proj: "Quark", projColor: c.green }, { id: "n5", title: "Expert Disclosure Draft — Christensen", folder: "nf3", mod: "5d", comments: 0 }, { id: "n6", title: "ELNOR Architecture Notes", folder: null, mod: "1w", comments: 0 }, ]; const todoLists = [ { id: "tl1", title: "Today", icon: "☀️", count: 5 }, { id: "tl2", title: "Henderson Tasks", icon: "⚖️", count: 8 }, { id: "tl3", title: "Weekly Review", icon: "📋", count: 3 }, ]; // ─── TO-DO DATA ─── const initTodos = [ { id: "t1", text: "Draft expert disclosure for Christensen", done: false, pri: 1, due: "Apr 4", proj: "Folb", sub: [ { id: "s1", text: "Pull Christensen CV", done: true }, { id: "s2", text: "List opinions", done: false }, { id: "s3", text: "Identify docs reviewed", done: false } ]}, { id: "t2", text: "Review Henderson MSJ brief — Elnor's tracked changes", done: false, pri: 1, due: "Mar 20", proj: "Henderson", sub: [] }, { id: "t3", text: "Respond to Sparacino re: Narayanan agreement", done: false, pri: 2, due: "Mar 19", sub: [] }, { id: "t4", text: "Follow up: opposing counsel discovery extension", done: false, pri: 2, due: "Mar 19", proj: "Henderson", sub: [] }, { id: "t5", text: "Review Quark patent analysis — new filings", done: false, pri: 3, proj: "Quark", sub: [] }, { id: "t6", text: "File Folb trial exhibit list", done: true, pri: 2, due: "Mar 17", proj: "Folb", sub: [] }, ]; // ─── ELNOR FEED DATA ─── const elnorFeed = [ { id: "e1", text: "Reviewed 3 Henderson deposition transcripts — flagged 2 citation issues in Chen depo", time: "5h ago", type: "task" }, { id: "e2", text: "Updated Quark patent landscape — 4 new filings", time: "4h ago", type: "task" }, { id: "e3", text: "Expert disclosure deadline — Folb v. City of LA", time: "47 days", type: "deadline", accent: c.error }, { id: "e4", text: "Henderson MSJ response due", time: "12 days", type: "deadline", accent: c.warn }, { id: "e5", text: "Sparacino — Narayanan agreement redline", time: "Yesterday", type: "email" }, { id: "e6", text: "Opposing counsel — discovery extension request", time: "2d ago", type: "email", accent: c.warn }, { id: "e7", text: "OneDrive sync paused — re-auth needed", time: "6h ago", type: "system", accent: c.warn }, { id: "e8", text: "9th Cir: new opinion on scienter standard", time: "Yesterday", type: "news", accent: c.accentBtn }, ]; // ─── NOTE CONTENT ─── const noteBody = [ { type: "h2", text: "Key Arguments to Strengthen" }, { type: "p", text: "1. Scienter — CFO Chen's \"do NOT share\" email is strongest evidence. Cross-ref with internal audit (Ex. 47). Elnor flagged additional Chen emails from July board prep." }, { type: "p", text: "2. Loss causation — need Bloomberg data for exact trading window. Stock drop 34% on Oct 15 disclosure." }, { type: "ai", author: "Elnor", text: "I found 3 additional Chen emails from the July board prep folder that strengthen scienter. Want me to pull the relevant excerpts into this note?", time: "2h ago" }, { type: "h2", text: "Outstanding Research" }, { type: "p", text: "- Check if Vivendi citation is still good law in 2d Cir — @Elnor reviewing" }, { type: "p", text: "- PSLRA safe harbor scope after Omnicare — need to distinguish" }, { type: "p", text: "- Damages calculation methodology — waiting on Christensen" }, { type: "h2", text: "Deposition Notes" }, { type: "p", text: "Chen depo key admissions: knew projections were stale by June 28, presented \"strong growth\" narrative anyway at July earnings call." }, ]; const initComments = [ { id: "c1", author: "You", color: c.accentBtn, body: "Strengthen scienter argument — Chen emails are key.", time: "2h", anchor: "CFO Chen's \"do NOT share\" email" }, { id: "c2", author: "Elnor", color: c.agentAv, body: "Found 3 additional Chen emails from July board prep. Cross-ref Ex. 47 for timeline.", time: "2h", isAgent: true }, { id: "c3", author: "You", color: c.accentBtn, body: "Check Vivendi cite — still good law?", time: "1h", anchor: "In re Vivendi Universal" }, ]; // ═══════════════════════════════════════════════════════════════ // MAIN COMPONENT // ═══════════════════════════════════════════════════════════════ export default function WorkspaceV2() { // Browser state const [activeScope, setActiveScope] = useState("Project"); const [activeProjIdx, setActiveProjIdx] = useState(0); const [activeTypes, setActiveTypes] = useState(new Set()); const [selectedCollections, setSelectedCollections] = useState(new Set()); const [sortKey, setSortKey] = useState("Modified"); const [sortDrop, setSortDrop] = useState(false); const [selectedBrowserIdx, setSelectedBrowserIdx] = useState(null); const [searchQ, setSearchQ] = useState(""); // Notes sidebar const [sidebarTab, setSidebarTab] = useState("notes"); // notes | todos const [expandedFolders, setExpandedFolders] = useState(new Set(["nf1"])); const [activeNoteId, setActiveNoteId] = useState("n1"); const [noteSearch, setNoteSearch] = useState(""); // Editor const [rightPanel, setRightPanel] = useState(null); // null | comments | send // To-do const [todos, setTodos] = useState(initTodos); const [expandedTodo, setExpandedTodo] = useState("t1"); const [todoCollapsed, setTodoCollapsed] = useState(false); const [elnorCollapsed, setElnorCollapsed] = useState(true); const [newTodoText, setNewTodoText] = useState(""); const [expandedElnor, setExpandedElnor] = useState(null); const toggleTodo = id => setTodos(p => p.map(t => t.id === id ? { ...t, done: !t.done } : t)); const toggleSub = (pid, sid) => setTodos(p => p.map(t => t.id === pid ? { ...t, sub: t.sub.map(s => s.id === sid ? { ...s, done: !s.done } : s) } : t)); const toggleFolder = id => setExpandedFolders(p => { const n = new Set(p); n.has(id) ? n.delete(id) : n.add(id); return n; }); const priColor = p => p === 1 ? c.error : p === 2 ? c.warn : c.accentBtn; const priLabel = p => p === 1 ? "P1" : p === 2 ? "P2" : "P3"; const activeTodos = todos.filter(t => !t.done); const doneTodos = todos.filter(t => t.done); const filtered = useMemo(() => { let items = browserItems; if (activeScope === "Project") items = items.filter(i => i.proj === projects[activeProjIdx].id); if (activeTypes.size > 0) items = items.filter(i => activeTypes.has(i.type)); if (searchQ) items = items.filter(i => i.title.toLowerCase().includes(searchQ.toLowerCase())); return items; }, [activeScope, activeProjIdx, activeTypes, searchQ]); // Render folder tree const renderFolders = (parentId, depth = 0) => { const children = noteFolders.filter(f => f.parent === parentId); return children.map(f => (
toggleFolder(f.id)} style={{ display: "flex", alignItems: "center", gap: 4, padding: "4px 6px", paddingLeft: 6 + depth * 14, cursor: "pointer", borderRadius: R.sm, fontSize: 11.5, color: c.textPri, fontWeight: expandedFolders.has(f.id) ? 600 : 450 }} onMouseEnter={e => e.currentTarget.style.backgroundColor = c.bgInput} onMouseLeave={e => e.currentTarget.style.backgroundColor = "transparent"}>
{f.title}
{expandedFolders.has(f.id) && renderFolders(f.id, depth + 1)}
)); }; const typeIcon = t => { if (t === "task") return ; if (t === "deadline") return ; if (t === "email") return ; if (t === "system") return ; if (t === "news") return ; return ; }; return (
{/* ═══ BROWSER COLUMN (full Q_BROWSER_R13 style) ═══ */}
{/* Search */}
setSearchQ(e.target.value)} placeholder="Search everything…" style={{ flex: 1, border: "none", outline: "none", backgroundColor: "transparent", fontSize: 11.5, fontFamily: font.sans, color: c.textPri }} /> {searchQ && }
{/* Scope row */}
{scopeChips.map(s => ( setActiveScope(activeScope === s ? null : s)} style={{ padding: "2px 7px", borderRadius: R.sm, border: `1px solid ${activeScope === s ? c.accentBtn + "50" : c.borderLight}`, fontSize: 9.5, color: activeScope === s ? c.accentBtn : c.textTer, backgroundColor: activeScope === s ? c.accentBtn + "08" : "transparent", cursor: "pointer", fontWeight: activeScope === s ? 600 : 400 }}>{s} ))}
{/* Scope detail */} {activeScope === "Project" && (
{projects.map((p, i) => (
setActiveProjIdx(i)} style={{ display: "flex", alignItems: "center", gap: 6, padding: "4px 8px", borderRadius: R.sm, cursor: "pointer", backgroundColor: i === activeProjIdx ? c.accentBtn + "08" : "transparent", fontWeight: i === activeProjIdx ? 600 : 400 }} onMouseEnter={e => { if (i !== activeProjIdx) e.currentTarget.style.backgroundColor = c.bgInput }} onMouseLeave={e => { e.currentTarget.style.backgroundColor = i === activeProjIdx ? c.accentBtn + "08" : "transparent" }}> {p.name}
))}
)} {/* Collection dots */}
{collections.map(col => ( setSelectedCollections(p => { const n = new Set(p); n.has(col.name) ? n.delete(col.name) : n.add(col.name); return n; })} title={col.name} style={{ width: 10, height: 10, borderRadius: "50%", backgroundColor: col.color, cursor: "pointer", border: selectedCollections.has(col.name) ? `2px solid ${c.textPri}` : "2px solid transparent", boxSizing: "border-box" }} /> ))} Collections
{/* Type chips */}
{typeChips.map(t => ( setActiveTypes(p => { const n = new Set(p); n.has(t) ? n.delete(t) : n.add(t); return n; })} style={{ padding: "1px 6px", borderRadius: 3, fontSize: 9, color: activeTypes.has(t) ? typeColors[t] || c.accentBtn : c.textTer, border: `1px solid ${activeTypes.has(t) ? (typeColors[t] || c.accentBtn) + "50" : c.borderLight}`, backgroundColor: activeTypes.has(t) ? (typeColors[t] || c.accentBtn) + "08" : "transparent", cursor: "pointer", fontWeight: activeTypes.has(t) ? 600 : 400 }}>{t} ))} {activeTypes.size > 0 && setActiveTypes(new Set())} style={{ fontSize: 9, color: c.textTer, cursor: "pointer", padding: "1px 4px" }}>clear}
{/* Sort bar */}
{filtered.length} items setSortDrop(!sortDrop)} style={{ cursor: "pointer", display: "flex", alignItems: "center", gap: 2 }}>{sortKey} {sortDrop &&
{sortOptions.map(s =>
{ setSortKey(s); setSortDrop(false) }} style={{ padding: "4px 8px", fontSize: 10, cursor: "pointer", borderRadius: 3, backgroundColor: s === sortKey ? c.accentBtn + "08" : "transparent", fontWeight: s === sortKey ? 600 : 400, color: s === sortKey ? c.accentBtn : c.textSec }} onMouseEnter={e => { if (s !== sortKey) e.currentTarget.style.backgroundColor = c.bgInput }} onMouseLeave={e => { e.currentTarget.style.backgroundColor = s === sortKey ? c.accentBtn + "08" : "transparent" }}>{s}
)}
}
{/* Results */}
{filtered.map((item, i) => (
setSelectedBrowserIdx(i)} style={{ display: "flex", alignItems: "center", gap: 6, padding: "6px 8px", height: 32, borderBottom: `1px solid ${c.borderLight}`, cursor: "pointer", backgroundColor: selectedBrowserIdx === i ? c.accentBtn + "08" : "transparent" }} onMouseEnter={e => { if (selectedBrowserIdx !== i) e.currentTarget.style.backgroundColor = c.bgPanelAlt }} onMouseLeave={e => { if (selectedBrowserIdx !== i) e.currentTarget.style.backgroundColor = "transparent" }}> {item.pin && } {item.unread && } {item.title} {item.st && {item.st}} {item.type} {item.time}
))} {filtered.length === 0 &&
No items match
}
{/* Footer */}
{activeScope && {activeScope}} {activeScope === "Project" && · {projects[activeProjIdx].name}} {selectedCollections.size > 0 && · {[...selectedCollections].join(", ")}} {sortKey}
{/* ═══ NOTES SIDEBAR (from Q_NOTES_FULL) ═══ */}
{/* Tab bar: Notes | To-Do Lists */}
{[{ id: "notes", label: "Notes", icon: }, { id: "todos", label: "To-Do Lists", icon: }].map(tab => ( ))}
{sidebarTab === "notes" && <> {/* Search + sort */}
setNoteSearch(e.target.value)} placeholder="Search notes…" style={{ flex: 1, border: "none", outline: "none", backgroundColor: "transparent", fontSize: 10.5, fontFamily: font.sans }} />
Modified ▾
{/* Folder tree */}
{renderFolders(null)}
{/* Note rows */} {noteList.map(note => (
setActiveNoteId(note.id)} style={{ display: "flex", alignItems: "center", gap: 5, padding: "5px 8px", cursor: "pointer", borderLeft: activeNoteId === note.id ? `3px solid ${c.accentBtn}` : "3px solid transparent", backgroundColor: activeNoteId === note.id ? c.accentBtn + "06" : "transparent" }} onMouseEnter={e => { if (activeNoteId !== note.id) e.currentTarget.style.backgroundColor = c.bgInput }} onMouseLeave={e => { if (activeNoteId !== note.id) e.currentTarget.style.backgroundColor = "transparent" }}> {note.pinned && }
{note.title}
{note.proj && <>{note.proj}} {note.mod} {note.comments > 0 && 💬{note.comments}} {note.pending > 0 && ✏{note.pending}}
))}
{noteList.length} notes · Modified
} {sidebarTab === "todos" && <>
{todoLists.map(list => (
{ if (list.id !== "tl1") e.currentTarget.style.backgroundColor = c.bgInput }} onMouseLeave={e => { if (list.id !== "tl1") e.currentTarget.style.backgroundColor = list.id === "tl1" ? c.accentBtn + "06" : "transparent" }}> {list.icon} {list.title} {list.count}
))}
}
{/* ═══ MAIN WORKSPACE ═══ */}
{/* ─── FULL NOTES TOOLBAR (from Q_NOTES_FULL) ─── */}
{/* Paragraph selector */} B} title="Bold" /> I} title="Italic" /> U} title="Underline" /> } title="Undo" /> } title="Redo" /> } title="Copy" /> } label="Save As…" dropdown /> } label="Ref" title="Copy reference" /> } title="Find (⌘F)" /> } title="Full Screen" /> } label="3" active={rightPanel === "comments"} onClick={() => setRightPanel(rightPanel === "comments" ? null : "comments")} /> } label="Markup" active title="Markup / Clean toggle" /> } label="2 ▾" title="Review: Accept All / Reject All" />
} label="Send to Agent" active={rightPanel === "send"} onClick={() => setRightPanel(rightPanel === "send" ? null : "send")} />
● Saved
{/* ─── CONTENT SPLIT: Editor + ToDo/Elnor column ─── */}
{/* ─── NOTE EDITOR ─── */}
Henderson MSJ — Work Notes
{noteBody.map((block, i) => { if (block.type === "h2") return

{block.text}

; if (block.type === "ai") return (
Elnor {block.time}
{block.text}
Yes Dismiss Reply
); return

{block.text}

; })}
{/* Note footer */}
Elnor in Henderson discovery ·Henderson v. DataCorp ·2h ago ·2 pending changes
{/* ─── TO-DO + ELNOR COLUMN ─── */}
{/* === MY TASKS === */}
setTodoCollapsed(!todoCollapsed)} style={{ padding: "7px 10px", display: "flex", alignItems: "center", gap: 6, cursor: "pointer", backgroundColor: c.bgPanel }}>
Today {activeTodos.length}
{!todoCollapsed && (
{activeTodos.map(todo => (
setExpandedTodo(expandedTodo === todo.id ? null : todo.id)}> toggleTodo(todo.id)} onClick={e => e.stopPropagation()} style={{ width: 13, height: 13, accentColor: c.accentBtn, marginTop: 2, flexShrink: 0 }} />
{todo.text}
{priLabel(todo.pri)} {todo.due && {todo.due}} {todo.proj && {todo.proj}} {todo.sub.length > 0 && {todo.sub.filter(s => s.done).length}/{todo.sub.length}}
{expandedTodo === todo.id && (
{todo.sub.map(s => (
toggleSub(todo.id, s.id)} style={{ width: 11, height: 11, accentColor: c.accentBtn }} /> {s.text}
))}
@Elnor Sub Note
)}
))} {/* Add task */}
setNewTodoText(e.target.value)} placeholder="Add task… (type @Elnor to assign)" onKeyDown={e => { if (e.key === "Enter" && newTodoText.trim()) { setTodos(p => [...p, { id: "t" + Date.now(), text: newTodoText, done: false, pri: 3, sub: [] }]); setNewTodoText(""); } }} style={{ flex: 1, border: "none", outline: "none", fontSize: 10.5, fontFamily: font.sans, backgroundColor: "transparent" }} />
{/* Done */} {doneTodos.length > 0 && (
Done ({doneTodos.length})
{doneTodos.map(t => (
toggleTodo(t.id)} style={{ width: 12, height: 12, accentColor: c.accentBtn }} /> {t.text}
))}
)}
{/* === ELNOR FEED === */}
setElnorCollapsed(!elnorCollapsed)} style={{ padding: "7px 10px", display: "flex", alignItems: "center", gap: 6, cursor: "pointer", backgroundColor: c.bgPanel }}>
Elnor {elnorFeed.length}
{!elnorCollapsed && (
{elnorFeed.map(item => (
setExpandedElnor(expandedElnor === item.id ? null : item.id)} style={{ display: "flex", alignItems: "flex-start", gap: 5, padding: "4px 6px", borderRadius: R.sm, cursor: "pointer", marginBottom: 1 }} onMouseEnter={e => e.currentTarget.style.backgroundColor = c.bgInput} onMouseLeave={e => e.currentTarget.style.backgroundColor = "transparent"}> {typeIcon(item.type)} {item.accent && } {item.text} {item.time}
))}
)}
)}
{/* ─── RIGHT PANEL (Comments / Send to Agent) ─── */} {rightPanel && (
{rightPanel === "comments" && initComments.map((cm, i) => (
{cm.author} {cm.time}
{cm.anchor &&
"{cm.anchor}"
}
{cm.body}
Reply Resolve ✨ Send
))} {rightPanel === "comments" && (
Add comment… (type @Elnor)
)} {rightPanel === "send" && (
Agent
Elnor
Output mode
{[{ l: "Respond in chat", d: "Opens chat with context" }, { l: "Send with instructions", d: "Agent processes result" }].map((o, i) => (
{o.l}
{o.d}
))}
Open in Chat
)}
)}
{/* ═══ CHAT COLUMN INDICATOR ═══ */}
CHAT →
Henderson
); }