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 }) => (
);
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 */}
{/* 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 */}
{/* 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.anchor &&
"{cm.anchor}"
}
{cm.body}
Reply
Resolve
✨ Send
))}
{rightPanel === "comments" && (
Add comment… (type @Elnor)
)}
{rightPanel === "send" && (
Output mode
{[{ l: "Respond in chat", d: "Opens chat with context" }, { l: "Send with instructions", d: "Agent processes result" }].map((o, i) => (
))}
Open in Chat
)}
)}
{/* ═══ CHAT COLUMN INDICATOR ═══ */}
);
}