Files
MosaicIQ/design-doc-v3.html

2235 lines
131 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8"/>
<meta name="viewport" content="width=device-width,initial-scale=1"/>
<title>MosaicIQ — Design Document v3</title>
<style>
:root {
--bg: oklch(97% 0.012 80);
--surface: oklch(99% 0.005 80);
--fg: oklch(20% 0.02 60);
--muted: oklch(48% 0.015 60);
--border: oklch(89% 0.012 80);
--accent: oklch(58% 0.16 35);
--green: oklch(52% 0.12 145);
--red: oklch(52% 0.14 25);
--font-display: 'Iowan Old Style', 'Charter', Georgia, serif;
--font-body: -apple-system, BlinkMacSystemFont, 'Segoe UI', system-ui, sans-serif;
--font-mono: ui-monospace, 'IBM Plex Mono', Menlo, monospace;
}
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
body {
background: var(--bg);
color: var(--fg);
font: 15px/1.7 var(--font-body);
-webkit-font-smoothing: antialiased;
}
/* ─── MASTHEAD ─── */
.masthead {
border-bottom: 1px solid var(--border);
padding: 16px 32px;
display: flex;
align-items: center;
gap: 32px;
background: var(--surface);
position: sticky;
top: 0;
z-index: 100;
}
.masthead .wordmark {
font: 700 16px/1 var(--font-display);
letter-spacing: -0.01em;
color: var(--fg);
}
.masthead .wordmark span { color: var(--accent); }
.masthead nav { display: flex; gap: 24px; }
.masthead nav a {
font: 500 12px/1 var(--font-body);
color: var(--muted);
text-decoration: none;
letter-spacing: 0.02em;
transition: color 0.15s;
}
.masthead nav a:hover { color: var(--fg); }
.masthead .badge {
font: 600 9px/1 var(--font-mono);
text-transform: uppercase;
letter-spacing: 0.08em;
color: var(--accent);
border: 1px solid var(--accent);
padding: 3px 8px;
margin-left: auto;
}
/* ─── ARTICLE ─── */
.article {
max-width: 720px;
margin: 0 auto;
padding: 48px 28px 120px;
}
.eyebrow {
font: 600 10px/1 var(--font-mono);
text-transform: uppercase;
letter-spacing: 0.1em;
color: var(--accent);
margin-bottom: 14px;
}
h1 {
font: 700 clamp(36px, 5vw, 52px)/1.1 var(--font-display);
letter-spacing: -0.02em;
margin-bottom: 16px;
}
.deck {
font: 400 20px/1.5 var(--font-display);
color: var(--muted);
margin-bottom: 28px;
}
.byline {
font: 400 13px/1.5 var(--font-body);
color: var(--muted);
margin-bottom: 48px;
display: flex;
align-items: center;
gap: 10px;
}
.byline .avatar {
width: 28px; height: 28px;
border-radius: 50%;
background: var(--accent);
opacity: 0.18;
flex-shrink: 0;
}
.hero-block {
aspect-ratio: 16/9;
background: linear-gradient(135deg, oklch(92% 0.02 80), oklch(88% 0.04 60));
border: 1px solid var(--border);
margin-bottom: 8px;
display: flex;
align-items: center;
justify-content: center;
font: 500 14px/1.4 var(--font-mono);
color: var(--muted);
letter-spacing: 0.04em;
}
.hero-caption {
font: 400 11px/1.4 var(--font-mono);
color: var(--muted);
margin-bottom: 48px;
}
/* ─── BODY ─── */
h2 {
font: 700 28px/1.2 var(--font-display);
letter-spacing: -0.01em;
margin: 56px 0 16px;
padding-top: 24px;
border-top: 1px solid var(--border);
}
h2:first-of-type { margin-top: 0; border-top: none; padding-top: 0; }
h3 {
font: 600 18px/1.3 var(--font-display);
margin: 32px 0 10px;
}
h4 {
font: 600 13px/1.4 var(--font-mono);
text-transform: uppercase;
letter-spacing: 0.06em;
color: var(--muted);
margin: 24px 0 8px;
}
p { margin: 16px 0; }
.body-first::first-letter {
float: left;
font-size: 56px;
line-height: 0.9;
padding: 6px 10px 0 0;
font-weight: 700;
color: var(--accent);
font-family: var(--font-display);
}
/* ─── PULL QUOTE ─── */
blockquote {
margin: 40px 0;
padding: 0 0 0 28px;
border-left: 3px solid var(--accent);
font: italic 22px/1.45 var(--font-display);
color: var(--fg);
}
/* ─── CODE / MONO ─── */
code {
font-family: var(--font-mono);
background: var(--surface);
border: 1px solid var(--border);
padding: 1px 5px;
font-size: 0.88em;
}
pre {
background: var(--surface);
border: 1px solid var(--border);
padding: 16px 18px;
overflow-x: auto;
font: 13px/1.55 var(--font-mono);
margin: 20px 0;
color: var(--fg);
}
/* ─── FIGURE ─── */
figure {
margin: 36px 0;
border: 1px solid var(--border);
padding: 24px;
background: var(--surface);
}
figure figcaption {
font: 400 11px/1.4 var(--font-mono);
color: var(--muted);
margin-top: 14px;
text-transform: uppercase;
letter-spacing: 0.04em;
}
/* ─── LISTS ─── */
ul, ol { margin: 14px 0; padding-left: 28px; }
li { margin: 6px 0; }
li::marker { color: var(--muted); }
/* ─── SPEC TABLE ─── */
.spec-table {
width: 100%;
border-collapse: collapse;
font: 400 13px/1.5 var(--font-body);
margin: 16px 0;
}
.spec-table th {
font: 600 10px/1 var(--font-mono);
text-transform: uppercase;
letter-spacing: 0.06em;
color: var(--muted);
text-align: left;
padding: 8px 12px;
border-bottom: 1px solid var(--fg);
background: var(--surface);
}
.spec-table td {
padding: 8px 12px;
border-bottom: 1px solid var(--border);
vertical-align: top;
}
.spec-table td:first-child {
font-weight: 600;
white-space: nowrap;
}
.spec-table code { border: none; background: none; padding: 0; font-size: 0.85em; color: var(--accent); }
/* ─── DIAGRAM BLOCK ─── */
.diagram {
border: 1px solid var(--border);
padding: 28px;
background: var(--surface);
margin: 28px 0;
overflow-x: auto;
}
.diagram h4 {
margin: 0 0 16px;
font: 600 10px/1 var(--font-mono);
text-transform: uppercase;
letter-spacing: 0.08em;
color: var(--muted);
}
.diagram svg { width: 100%; height: auto; }
/* ─── TOKEN SWATCHES ─── */
.swatches {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(140px, 1fr));
gap: 1px;
background: var(--border);
border: 1px solid var(--border);
margin: 16px 0;
}
.swatch {
background: var(--surface);
padding: 14px;
}
.swatch-color {
width: 100%;
height: 40px;
border: 1px solid var(--border);
margin-bottom: 8px;
}
.swatch-name {
font: 600 10px/1 var(--font-mono);
text-transform: uppercase;
letter-spacing: 0.06em;
color: var(--muted);
margin-bottom: 4px;
}
.swatch-val {
font: 400 11px/1.3 var(--font-mono);
color: var(--fg);
}
/* ─── SCREEN REF ─── */
.screen-ref {
display: grid;
grid-template-columns: auto 1fr;
gap: 6px 16px;
font-size: 13.5px;
margin: 10px 0;
padding: 12px 0;
border-bottom: 1px solid oklch(93% 0.008 80);
}
.screen-ref dt {
font: 600 10px/1.6 var(--font-mono);
text-transform: uppercase;
letter-spacing: 0.06em;
color: var(--muted);
}
.screen-ref dd { line-height: 1.6; }
/* ─── NUMBERED FIGURE ─── */
.stat-row {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 1px;
background: var(--border);
border: 1px solid var(--border);
margin: 28px -24px;
}
.stat-cell {
background: var(--surface);
padding: 24px;
text-align: center;
}
.stat-cell .stat-val {
font: 700 32px/1 var(--font-display);
letter-spacing: -0.02em;
}
.stat-cell .stat-label {
font: 500 10px/1 var(--font-mono);
text-transform: uppercase;
letter-spacing: 0.06em;
color: var(--muted);
margin-top: 6px;
}
/* ─── DIVIDER ─── */
hr {
border: none;
border-top: 1px solid var(--border);
margin: 40px 0;
}
/* ─── ENDNOTE ─── */
.endnote {
font: 400 13px/1.6 var(--font-body);
color: var(--muted);
margin-top: 64px;
padding-top: 24px;
border-top: 1px solid var(--border);
}
.endnote a { color: var(--accent); text-decoration: none; }
.endnote a:hover { text-decoration: underline; }
/* ─── RELATED ─── */
.related {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 1px;
background: var(--border);
border: 1px solid var(--border);
margin-top: 48px;
}
.related-card {
background: var(--surface);
padding: 20px;
}
.related-card .rc-thumb {
width: 100%;
height: 80px;
background: var(--border);
margin-bottom: 12px;
}
.related-card .rc-title {
font: 600 14px/1.3 var(--font-display);
margin-bottom: 4px;
}
.related-card .rc-excerpt {
font-size: 12px;
color: var(--muted);
line-height: 1.4;
margin-bottom: 8px;
}
.related-card .rc-date {
font: 400 10px/1 var(--font-mono);
color: var(--muted);
text-transform: uppercase;
letter-spacing: 0.06em;
}
/* ─── TOC SIDEBAR ─── */
.toc-float {
position: fixed;
top: 80px;
right: 32px;
width: 200px;
font: 400 11px/1.7 var(--font-body);
color: var(--muted);
}
.toc-float .toc-title {
font: 600 9px/1 var(--font-mono);
text-transform: uppercase;
letter-spacing: 0.1em;
margin-bottom: 8px;
color: var(--fg);
}
.toc-float a {
display: block;
color: var(--muted);
text-decoration: none;
transition: color 0.15s;
}
.toc-float a:hover { color: var(--fg); }
@media (max-width: 1100px) { .toc-float { display: none; } }
/* ─── NEW-SECTION TAG ─── */
.new-tag {
display: inline-block;
font: 600 8px/1 var(--font-mono);
text-transform: uppercase;
letter-spacing: 0.08em;
color: var(--green);
border: 1px solid var(--green);
padding: 2px 6px;
margin-left: 8px;
vertical-align: middle;
}
.v3-tag {
display: inline-block;
font: 600 8px/1 var(--font-mono);
text-transform: uppercase;
letter-spacing: 0.08em;
color: var(--accent);
border: 1px solid var(--accent);
padding: 2px 6px;
margin-left: 8px;
vertical-align: middle;
}
/* ─── RPC METHOD TABLE ─── */
.rpc-table {
width: 100%;
border-collapse: collapse;
font: 400 12px/1.5 var(--font-mono);
margin: 16px 0;
}
.rpc-table th {
font: 600 9px/1 var(--font-mono);
text-transform: uppercase;
letter-spacing: 0.06em;
color: var(--muted);
text-align: left;
padding: 8px 10px;
border-bottom: 1px solid var(--fg);
background: var(--surface);
}
.rpc-table td {
padding: 6px 10px;
border-bottom: 1px solid var(--border);
vertical-align: top;
font-size: 11.5px;
}
.rpc-table td:first-child { color: var(--accent); white-space: nowrap; font-weight: 600; }
.rpc-table td:nth-child(2) { color: var(--muted); font-size: 10.5px; }
</style>
</head>
<body>
<!-- ═══ MASTHEAD ═══ -->
<div class="masthead">
<div class="wordmark">MosaicIQ <span>·</span> Design System</div>
<nav>
<a href="#overview">Overview</a>
<a href="#tokens">Tokens</a>
<a href="#architecture">Architecture</a>
<a href="#screens">Screens</a>
<a href="#components">Components</a>
<a href="#agents">Agents</a>
<a href="#interactive-states">States</a>
<a href="#interactions">Interactions</a>
</nav>
<div class="badge">Technical RFC · v3.0</div>
</div>
<!-- ═══ TOC ═══ -->
<div class="toc-float">
<div class="toc-title">Contents</div>
<a href="#overview">1. Overview</a>
<a href="#tokens">2. Design Tokens</a>
<a href="#architecture">3. System Architecture</a>
<a href="#data-layer">3.1 Data Layer</a>
<a href="#rpc-surface">3.2 RPC Surface</a>
<a href="#realtime-transport">3.3 Real-time Transport</a>
<a href="#state-shape">3.4 State Shape</a>
<a href="#persistence">3.5 Persistence & Versioning</a>
<a href="#first-run">3.6 First-Run Experience</a>
<a href="#screens">4. Screen Specifications</a>
<a href="#screen-home">4.1 Home Dashboard</a>
<a href="#screen-workspace">4.2 Company Workspace</a>
<a href="#screen-model">4.3 Financial Model</a>
<a href="#screen-memo">4.4 Memo Editor</a>
<a href="#screen-agents">4.5 Agent Orchestration</a>
<a href="#screen-catalyst">4.6 Catalyst Tracker</a>
<a href="#screen-thesis-alerts">4.7 Thesis Alerts</a>
<a href="#screen-risk-register">4.8 Risk Register</a>
<a href="#screen-earnings">4.9 Earnings Monitor</a>
<a href="#screen-filing-watch">4.10 Filing Watch</a>
<a href="#screen-export">4.11 Export Center</a>
<a href="#components">5. Component Library</a>
<a href="#agents">6. Agent System</a>
<a href="#collaborator">6.1 Agents as Collaborators</a>
<a href="#agent-interaction">6.2 Agent Interaction Model</a>
<a href="#agent-configure">6.3 Agent Configuration Panel</a>
<a href="#validation">7. Research & Validation Cycle</a>
<a href="#validation-flow">7.1 Validation Flow Diagram</a>
<a href="#interactive-states">8. Interactive States</a>
<a href="#loading-states">8.1 Loading & Skeleton States</a>
<a href="#empty-states">8.2 Empty States</a>
<a href="#error-states">8.3 Error States</a>
<a href="#toast-system">8.4 Toast & Notifications</a>
<a href="#search-behavior">8.5 Search Behavior</a>
<a href="#interactions">9. Interaction Patterns</a>
<a href="#keyboard-shortcuts">9.1 Keyboard Shortcuts</a>
<a href="#dark-mode">9.2 Dark Mode</a>
<a href="#transitions">9.3 Screen Transitions</a>
<a href="#accessibility">9.4 Accessibility</a>
<a href="#responsive">9.5 Responsive Behavior</a>
<a href="#density">9.6 Data Density Settings</a>
</div>
<!-- ═══ ARTICLE ═══ -->
<article class="article" data-od-id="article">
<div class="eyebrow">Technical Design Doc · RFC-003</div>
<h1 data-od-id="headline">MosaicIQ: An Equity Research Workspace</h1>
<p class="deck">A full-stack architecture specification for an AI-native equity research platform — local-first server/client sandboxing, portfolio dashboards, financial models, investment memos, and a 14-agent collaborative pipeline where every agent feels like a trusted co-analyst, with built-in research & validation cycles — built on an editorial design system.</p>
<div class="byline">
<div class="avatar"></div>
<span>Authored by MosaicIQ Design · May 2026 · For engineering implementation</span>
</div>
<div class="hero-block" data-od-id="hero">System architecture · 5 screens · 14 collaborative agents · sandboxed server/client · research & validation loops · 28 RPC methods</div>
<div class="hero-caption">Fig 1. MosaicIQ at a glance: a sandboxed client/server architecture with five primary screens feeding a persistent collaborative agent pipeline. Every output passes through a research-then-validate cycle before reaching the analyst.</div>
<!-- ═══════════════════════════════════════════ -->
<h2 id="overview">1. Overview</h2>
<p class="body-first">MosaicIQ is a single-page application that provides buy-side analysts with a unified workspace for equity research. It combines portfolio monitoring, company fundamental data, financial modeling, investment memo authoring, and AI agent orchestration into one editorial-grade interface. The design language borrows from print magazine conventions — serif display type, monospaced data, generous whitespace, restrained accent — to make financial content legible and authoritative.</p>
<p>The platform targets desktop web users working on 1440p+ displays. It is not a mobile-first product. Information density is a feature: analysts need to see holdings, charts, and factor tilts simultaneously without switching tabs.</p>
<h4>Naming convention <span class="v3-tag">v3</span></h4>
<p>The product name is <strong>MosaicIQ</strong> — this is the canonical name used in all user-facing text, documentation, and brand materials. "Meridian" appears in the prototype as an internal codename for the shell component; it should be replaced with "MosaicIQ" in implementation. The profile editor, logo, and topbar should all read "MosaicIQ".</p>
<figure class="stat-row">
<div class="stat-cell">
<div class="stat-val">5</div>
<div class="stat-label">Primary Screens</div>
</div>
<div class="stat-cell">
<div class="stat-val">14</div>
<div class="stat-label">AI Agents</div>
</div>
<div class="stat-cell">
<div class="stat-val">28</div>
<div class="stat-label">RPC Methods</div>
</div>
</figure>
<h3>Core principles</h3>
<ol>
<li><strong>Editorial posture.</strong> Serif display headings, monospace for all numerical data, sans-serif body. No rounded cards, no drop shadows, no gradients — borders and whitespace do the structural work.</li>
<li><strong>Agent-native, agent-as-collaborator.</strong> AI agents are not a sidebar feature — they are the primary production pipeline, and they should <em>feel like collaborators</em>, not tools. Every agent interaction is designed to mirror working with a skilled co-analyst: they surface assumptions, flag uncertainty, ask clarifying questions, and present findings for review rather than delivering final answers unilaterally.</li>
<li><strong>Local-first, sandboxed.</strong> The application is single-user and local-first. All data and the agent sandbox run locally with a clean server/client boundary — the client never reads storage directly, it goes through a typed RPC layer (§3.2). This architecture mirrors t3-code's approach and keeps the path open for future remote server support without changing the client.</li>
<li><strong>Spreadsheet-grade data.</strong> Financial models use tabular monospace with hairline borders, sticky headers, and explicit distinction between actuals (solid type) and forecasts (italic, muted).</li>
<li><strong>One accent, used twice.</strong> A single rust accent color appears on eyebrows and pull-quote rules. Green and red are reserved for directional meaning (positive/negative). All other color is from the near-greyscale palette.</li>
<li><strong>Research + validation cycles.</strong> No agent output ships without a structured validation loop. Every pipeline stage ends with a review checkpoint: sources are verified, assumptions are stress-tested by the Red Team agent, and the analyst can accept, reject, or revise any finding before it propagates downstream.</li>
<li><strong>Collapsible chrome.</strong> Left navigation panels collapse to 36px to give maximum space to content. The command bar persists across all screens as the agent control surface.</li>
</ol>
<!-- ═══════════════════════════════════════════ -->
<h2 id="tokens">2. Design Tokens</h2>
<p>All colors are specified in OKLch for perceptual uniformity. The palette is warm-neutral with a single saturated accent. No shadows, no gradients in production UI.</p>
<div class="swatches">
<div class="swatch">
<div class="swatch-color" style="background:oklch(97% 0.012 80)"></div>
<div class="swatch-name">--bg</div>
<div class="swatch-val">oklch(97% 0.012 80)</div>
</div>
<div class="swatch">
<div class="swatch-color" style="background:oklch(99% 0.005 80)"></div>
<div class="swatch-name">--surface</div>
<div class="swatch-val">oklch(99% 0.005 80)</div>
</div>
<div class="swatch">
<div class="swatch-color" style="background:oklch(20% 0.02 60)"></div>
<div class="swatch-name">--fg</div>
<div class="swatch-val">oklch(20% 0.02 60)</div>
</div>
<div class="swatch">
<div class="swatch-color" style="background:oklch(48% 0.015 60)"></div>
<div class="swatch-name">--muted</div>
<div class="swatch-val">oklch(48% 0.015 60)</div>
</div>
<div class="swatch">
<div class="swatch-color" style="background:oklch(89% 0.012 80)"></div>
<div class="swatch-name">--border</div>
<div class="swatch-val">oklch(89% 0.012 80)</div>
</div>
<div class="swatch">
<div class="swatch-color" style="background:oklch(58% 0.16 35)"></div>
<div class="swatch-name">--accent</div>
<div class="swatch-val">oklch(58% 0.16 35)</div>
</div>
<div class="swatch">
<div class="swatch-color" style="background:oklch(52% 0.12 145)"></div>
<div class="swatch-name">--green</div>
<div class="swatch-val">oklch(52% 0.12 145)</div>
</div>
<div class="swatch">
<div class="swatch-color" style="background:oklch(52% 0.14 25)"></div>
<div class="swatch-name">--red</div>
<div class="swatch-val">oklch(52% 0.14 25)</div>
</div>
</div>
<h3>Typography</h3>
<table class="spec-table">
<thead>
<tr><th>Token</th><th>Stack</th><th>Usage</th></tr>
</thead>
<tbody>
<tr><td><code>--font-display</code></td><td>Iowan Old Style, Charter, Georgia, serif</td><td>Headlines, metric values, pull quotes</td></tr>
<tr><td><code>--font-body</code></td><td>System UI stack (SF Pro, Segoe UI, etc.)</td><td>Body copy, button labels, navigation</td></tr>
<tr><td><code>--font-mono</code></td><td>IBM Plex Mono, Menlo, monospace</td><td>Tickers, numbers, tables, code, metadata</td></tr>
</tbody>
</table>
<h3>Spacing and radii</h3>
<ul>
<li>All radii are <code>2px</code> or <code>0</code>. No rounded cards, no pill shapes except the avatar circle.</li>
<li>Border weight is <code>1px solid var(--border)</code> universally. Total rows and section dividers use <code>1px solid var(--fg)</code>.</li>
<li>Content padding: <code>28px 36px</code> in the center column. <code>16px</code> in cards and metric cells.</li>
<li>The topbar is <code>48px</code> tall. The command bar is <code>52px</code> minimum.</li>
</ul>
<!-- ═══════════════════════════════════════════ -->
<h2 id="architecture">3. System Architecture</h2>
<p>MosaicIQ is structured as a fixed chrome shell with swappable screen content. The shell consists of three persistent elements — topbar, main area, and command bar — that never unmount. Screen transitions swap the contents of <code>.main</code>.</p>
<div class="diagram">
<h4>Fig 2. Shell Structure</h4>
<svg viewBox="0 0 680 380" xmlns="http://www.w3.org/2000/svg">
<!-- Topbar -->
<rect x="0" y="0" width="680" height="40" fill="oklch(99% 0.005 80)" stroke="oklch(89% 0.012 80)" stroke-width="1"/>
<text x="12" y="25" style="font:600 11px var(--font-mono);fill:oklch(20% 0.02 60)">MosaicIQ</text>
<text x="100" y="25" style="font:400 10px var(--font-mono);fill:oklch(48% 0.015 60)">COST · $921.40</text>
<rect x="360" y="8" width="200" height="24" fill="oklch(97% 0.012 80)" stroke="oklch(89% 0.012 80)" stroke-width="1"/>
<text x="374" y="24" style="font:400 10px var(--font-body);fill:oklch(48% 0.015 60)">Search companies, files…</text>
<rect x="580" y="10" width="24" height="24" rx="12" fill="none" stroke="oklch(89% 0.012 80)" stroke-width="1.5"/>
<text x="586" y="26" style="font:600 9px var(--font-mono);fill:oklch(48% 0.015 60)">JD</text>
<text x="12" y="60" style="font:600 10px var(--font-mono);fill:oklch(48% 0.015 60);text-transform:uppercase;letter-spacing:0.08em">Topbar — 48px · sticky</text>
<!-- Main area -->
<rect x="0" y="80" width="180" height="220" fill="oklch(99% 0.005 80)" stroke="oklch(89% 0.012 80)" stroke-width="1"/>
<text x="12" y="100" style="font:500 9px var(--font-mono);fill:oklch(48% 0.015 60);text-transform:uppercase;letter-spacing:0.06em">Left Nav</text>
<text x="12" y="120" style="font:400 10px var(--font-body);fill:oklch(48% 0.015 60)">240px → 36px</text>
<text x="12" y="140" style="font:400 10px var(--font-body);fill:oklch(48% 0.015 60)">Collapsible</text>
<rect x="180" y="80" width="320" height="220" fill="oklch(97% 0.012 80)" stroke="oklch(89% 0.012 80)" stroke-width="1"/>
<text x="200" y="100" style="font:500 9px var(--font-mono);fill:oklch(48% 0.015 60);text-transform:uppercase;letter-spacing:0.06em">Center Content</text>
<text x="200" y="120" style="font:400 10px var(--font-body);fill:oklch(48% 0.015 60)">flex: 1 · scrollable</text>
<text x="200" y="140" style="font:400 10px var(--font-body);fill:oklch(48% 0.015 60)">Screen-dependent</text>
<rect x="500" y="80" width="180" height="220" fill="oklch(99% 0.005 80)" stroke="oklch(89% 0.012 80)" stroke-width="1"/>
<text x="512" y="100" style="font:500 9px var(--font-mono);fill:oklch(48% 0.015 60);text-transform:uppercase;letter-spacing:0.06em">Right Panel</text>
<text x="512" y="120" style="font:400 10px var(--font-body);fill:oklch(48% 0.015 60)">300360px</text>
<text x="512" y="140" style="font:400 10px var(--font-body);fill:oklch(48% 0.015 60)">Context-dependent</text>
<text x="12" y="330" style="font:600 10px var(--font-mono);fill:oklch(48% 0.015 60);text-transform:uppercase;letter-spacing:0.08em">Main Area — flex row · overflow hidden</text>
<!-- Command bar -->
<rect x="0" y="350" width="680" height="30" fill="oklch(99% 0.005 80)" stroke="oklch(89% 0.012 80)" stroke-width="1"/>
<circle cx="20" cy="365" r="5" fill="oklch(58% 0.16 35)"/>
<text x="32" y="369" style="font:400 10px var(--font-body);fill:oklch(48% 0.015 60)">Agent carousel · Chat prompt</text>
<text x="12" y="395" style="font:600 10px var(--font-mono);fill:oklch(48% 0.015 60);text-transform:uppercase;letter-spacing:0.08em">Command Bar — 52px · persistent</text>
</svg>
</div>
<h3>Screen routing</h3>
<p>Screens are CSS-toggled: only one <code>.screen</code> has <code>.active</code> at a time. The topbar tabs call <code>switchScreen(id)</code>, which removes <code>.active</code> from all screens and applies it to the target. No router, no URL changes — this is a single-page state machine.</p>
<table class="spec-table">
<thead>
<tr><th>Screen ID</th><th>Label</th><th>Layout</th><th>Left Nav</th><th>Right Panel</th></tr>
</thead>
<tbody>
<tr><td><code>home</code></td><td>Home</td><td>Full-width center</td><td>None</td><td>None</td></tr>
<tr><td><code>workspace</code></td><td>Workspace</td><td>Nav + center</td><td>Research sections (collapsible)</td><td>None</td></tr>
<tr><td><code>model</code></td><td>Model</td><td>Toolbar + center</td><td>None</td><td>None</td></tr>
<tr><td><code>memo</code></td><td>Memo</td><td>Nav + center + panel</td><td>Outline (collapsible)</td><td>Review tools + citations</td></tr>
<tr><td><code>agents</code></td><td>Agents</td><td>Toolbar + grid + detail</td><td>None</td><td>Detail panel (slide-in)</td></tr>
</tbody>
</table>
<!-- ═══════════════════════════════════════════ -->
<h3 id="data-layer">3.1 Data Layer</h3>
<p>The application is local-first, single-user. All data and the agent sandbox run locally via SQLite through a typed server layer. The client never reads or writes storage directly — it communicates through the RPC interface defined in §3.2.</p>
<div class="diagram">
<h4>Fig 3. Data Layer Architecture</h4>
<svg viewBox="0 0 680 220" xmlns="http://www.w3.org/2000/svg">
<!-- Client -->
<rect x="0" y="0" width="680" height="100" rx="2" fill="oklch(97% 0.012 80)" stroke="oklch(89% 0.012 80)" stroke-width="1"/>
<text x="14" y="18" style="font:600 9px var(--font-mono);fill:oklch(48% 0.015 60);text-transform:uppercase;letter-spacing:0.08em">Client (web / Electron shell)</text>
<rect x="14" y="28" width="200" height="60" rx="2" fill="oklch(99% 0.005 80)" stroke="oklch(89% 0.012 80)" stroke-width="1"/>
<text x="24" y="46" style="font:500 9px var(--font-mono);fill:oklch(20% 0.02 60)">React state (TanStack)</text>
<text x="24" y="60" style="font:400 8px var(--font-mono);fill:oklch(48% 0.015 60)">· queries cache</text>
<text x="24" y="72" style="font:400 8px var(--font-mono);fill:oklch(48% 0.015 60)">· active screen / panel</text>
<text x="24" y="84" style="font:400 8px var(--font-mono);fill:oklch(48% 0.015 60)">· agent carousel state</text>
<!-- Arrow -->
<line x1="220" y1="58" x2="260" y2="58" stroke="oklch(58% 0.16 35)" stroke-width="1.5"/>
<polygon points="258,55 258,61 265,58" fill="oklch(58% 0.16 35)"/>
<text x="225" y="50" style="font:500 7px var(--font-mono);fill:oklch(58% 0.16 35)">RPC</text>
<!-- RPC Layer -->
<rect x="268" y="38" width="140" height="40" rx="2" fill="oklch(96% 0.008 80)" stroke="oklch(58% 0.16 35)" stroke-width="1"/>
<text x="280" y="56" style="font:600 8px var(--font-mono);fill:oklch(58% 0.16 35)">Typed RPC interface</text>
<text x="280" y="68" style="font:400 7px var(--font-mono);fill:oklch(48% 0.015 60)">IPC / HTTP transport</text>
<!-- Arrow -->
<line x1="412" y1="58" x2="452" y2="58" stroke="oklch(58% 0.16 35)" stroke-width="1.5"/>
<polygon points="450,55 450,61 457,58" fill="oklch(58% 0.16 35)"/>
<!-- Server -->
<rect x="460" y="28" width="200" height="60" rx="2" fill="oklch(99% 0.005 80)" stroke="oklch(20% 0.02 60)" stroke-width="1"/>
<text x="472" y="46" style="font:600 9px var(--font-mono);fill:oklch(20% 0.02 60)">Local Server</text>
<text x="472" y="60" style="font:400 8px var(--font-mono);fill:oklch(48% 0.015 60)">· SQLite (better-sqlite3)</text>
<text x="472" y="72" style="font:400 8px var(--font-mono);fill:oklch(48% 0.015 60)">· Agent orchestration</text>
<text x="472" y="84" style="font:400 8px var(--font-mono);fill:oklch(48% 0.015 60)">· File I/O · Settings (JSON)</text>
<!-- Settings split -->
<rect x="14" y="120" width="320" height="90" rx="2" fill="oklch(96% 0.006 80)" stroke="oklch(89% 0.012 80)" stroke-width="1" stroke-dasharray="4 2"/>
<text x="24" y="138" style="font:600 8px var(--font-mono);fill:oklch(48% 0.015 60);text-transform:uppercase;letter-spacing:0.08em">Settings split (t3code convention)</text>
<rect x="24" y="148" width="145" height="50" rx="2" fill="oklch(99% 0.005 80)" stroke="oklch(89% 0.012 80)" stroke-width="1"/>
<text x="34" y="164" style="font:600 8px var(--font-mono);fill:oklch(58% 0.16 35)">ClientSettings</text>
<text x="34" y="176" style="font:400 7px var(--font-mono);fill:oklch(48% 0.015 60)">theme, density,</text>
<text x="34" y="186" style="font:400 7px var(--font-mono);fill:oklch(48% 0.015 60)">sidebar width → JSON file</text>
<rect x="179" y="148" width="145" height="50" rx="2" fill="oklch(99% 0.005 80)" stroke="oklch(89% 0.012 80)" stroke-width="1"/>
<text x="189" y="164" style="font:600 8px var(--font-mono);fill:oklch(20% 0.02 60)">ServerSettings</text>
<text x="189" y="176" style="font:400 7px var(--font-mono);fill:oklch(48% 0.015 60)">agent configs, data sources,</text>
<text x="189" y="186" style="font:400 7px var(--font-mono);fill:oklch(48% 0.015 60)">export pipelines → SQLite</text>
</svg>
</div>
<h4>Key convention from t3-code</h4>
<p>Settings split into <code>ClientSettings</code> (local UI prefs — theme, density, sidebar width) and <code>ServerSettings</code> (data-authoritative — agent configs, data sources, export pipelines). Client settings are a JSON file on disk; server settings live in SQLite. Both use schema-validated decode with defaults so missing keys never crash the app.</p>
<!-- ═══════════════════════════════════════════ -->
<h3 id="rpc-surface">3.2 RPC Surface <span class="v3-tag">v3</span></h3>
<p>The client communicates with the server exclusively through typed RPC methods. Transport is IPC in Electron, HTTP in browser. Every method has a typed request and response shape; the server validates inputs and returns structured errors. There are no REST endpoints — all calls go through a single <code>/rpc</code> entry point with a method name and JSON payload.</p>
<h4>Company & Portfolio</h4>
<table class="rpc-table">
<thead><tr><th>Method</th><th>Input</th><th>Output</th></tr></thead>
<tbody>
<tr><td>portfolio.get</td><td>{}</td><td>{ id, name, holdings[] }</td></tr>
<tr><td>portfolio.addHolding</td><td>{ ticker }</td><td>{ holding }</td></tr>
<tr><td>portfolio.removeHolding</td><td>{ ticker }</td><td>{ ok }</td></tr>
<tr><td>company.get</td><td>{ companyId }</td><td>{ company } — full profile + metrics</td></tr>
<tr><td>company.search</td><td>{ query }</td><td>{ results[] } — ticker, name, sector</td></tr>
<tr><td>company.setActive</td><td>{ companyId }</td><td>{ ok }</td></tr>
</tbody>
</table>
<h4>Workspace & Research</h4>
<table class="rpc-table">
<thead><tr><th>Method</th><th>Input</th><th>Output</th></tr></thead>
<tbody>
<tr><td>workspace.getSection</td><td>{ companyId, section }</td><td>{ content, validationState }</td></tr>
<tr><td>workspace.listSources</td><td>{ companyId }</td><td>{ sources[] }</td></tr>
<tr><td>catalyst.list</td><td>{ companyId }</td><td>{ catalysts[] }</td></tr>
<tr><td>alert.list</td><td>{ companyId?, since? }</td><td>{ alerts[] }</td></tr>
<tr><td>risk.list</td><td>{ companyId }</td><td>{ risks[] }</td></tr>
<tr><td>risk.add</td><td>{ companyId, risk }</td><td>{ risk }</td></tr>
<tr><td>earnings.getSchedule</td><td>{ companyId }</td><td>{ schedule[] }</td></tr>
<tr><td>filing.list</td><td>{ companyId, since? }</td><td>{ filings[] }</td></tr>
</tbody>
</table>
<h4>Financial Model</h4>
<table class="rpc-table">
<thead><tr><th>Method</th><th>Input</th><th>Output</th></tr></thead>
<tbody>
<tr><td>model.get</td><td>{ companyId, tab }</td><td>{ headers[], rows[] }</td></tr>
<tr><td>model.updateCell</td><td>{ companyId, tab, row, col, value }</td><td>{ ok, affectedCells[] }</td></tr>
<tr><td>model.runScenario</td><td>{ companyId, scenario, overrides }</td><td>{ headers[], rows[] }</td></tr>
</tbody>
</table>
<h4>Memo</h4>
<table class="rpc-table">
<thead><tr><th>Method</th><th>Input</th><th>Output</th></tr></thead>
<tbody>
<tr><td>memo.get</td><td>{ companyId }</td><td>{ sections[], status }</td></tr>
<tr><td>memo.updateSection</td><td>{ companyId, sectionId, content }</td><td>{ ok }</td></tr>
<tr><td>memo.annotate</td><td>{ companyId, sectionId, annotation }</td><td>{ annotation }</td></tr>
<tr><td>memo.acceptEdit</td><td>{ companyId, editId }</td><td>{ ok }</td></tr>
<tr><td>memo.rejectEdit</td><td>{ companyId, editId, reason? }</td><td>{ ok }</td></tr>
</tbody>
</table>
<h4>Agents</h4>
<table class="rpc-table">
<thead><tr><th>Method</th><th>Input</th><th>Output</th></tr></thead>
<tbody>
<tr><td>agent.list</td><td>{ companyId? }</td><td>{ agents[] } — status, progress, config</td></tr>
<tr><td>agent.start</td><td>{ agentId, companyId }</td><td>{ runId }</td></tr>
<tr><td>agent.pause</td><td>{ agentId }</td><td>{ ok }</td></tr>
<tr><td>agent.restart</td><td>{ agentId }</td><td>{ runId }</td></tr>
<tr><td>agent.chat</td><td>{ agentId, message }</td><td>{ response } — streamed via SSE</td></tr>
<tr><td>agent.configure</td><td>{ agentId, config }</td><td>{ ok }</td></tr>
<tr><td>agent.getTrace</td><td>{ agentId, runId }</td><td>{ steps[] }</td></tr>
<tr><td>agent.runPipeline</td><td>{ companyId, pipeline }</td><td>{ runIds[] }</td></tr>
</tbody>
</table>
<h4>Export & Settings</h4>
<table class="rpc-table">
<thead><tr><th>Method</th><th>Input</th><th>Output</th></tr></thead>
<tbody>
<tr><td>export.list</td><td>{ companyId? }</td><td>{ exports[] }</td></tr>
<tr><td>export.create</td><td>{ type, companyId, options }</td><td>{ exportId }</td></tr>
<tr><td>export.download</td><td>{ exportId }</td><td>binary stream</td></tr>
<tr><td>settings.get</td><td>{ scope: 'client' | 'server' }</td><td>{ settings }</td></tr>
<tr><td>settings.update</td><td>{ scope, changes }</td><td>{ ok }</td></tr>
</tbody>
</table>
<h4>Error contract</h4>
<p>Every RPC error returns a structured object: <code>{ code: string, message: string, detail?: any }</code>. Standard error codes: <code>NOT_FOUND</code>, <code>VALIDATION_ERROR</code>, <code>AGENT_FAILED</code>, <code>CONFLICT</code> (e.g. trying to start an already-running agent), <code>RATE_LIMITED</code>.</p>
<!-- ═══════════════════════════════════════════ -->
<h3 id="realtime-transport">3.3 Real-time Transport <span class="v3-tag">v3</span></h3>
<p>Agent streaming and live updates use <strong>Server-Sent Events (SSE)</strong> over a single persistent connection. SSE was chosen over WebSockets because: the transport is server-to-client dominant (agent output streaming), it works over HTTP without special proxying, and it reconnects automatically with a <code>Last-Event-ID</code> header.</p>
<h4>Event channel</h4>
<p>The client opens one SSE connection at startup: <code>GET /events?companyId={id}</code>. The server pushes typed events:</p>
<table class="spec-table">
<thead><tr><th>Event type</th><th>Payload</th><th>Frequency</th><th>Consumer</th></tr></thead>
<tbody>
<tr><td><code>agent.progress</code></td><td>{ agentId, percent, currentAction }</td><td>Every 12s during run</td><td>Agent grid tiles, carousel, detail panel</td></tr>
<tr><td><code>agent.output</code></td><td>{ agentId, chunk, outputType }</td><td>Streaming (token-by-token or block-by-block)</td><td>Fullscreen overlay output column, memo body</td></tr>
<tr><td><code>agent.status</code></td><td>{ agentId, oldState, newState }</td><td>On state transitions</td><td>Agent grid, carousel dot, toast triggers</td></tr>
<tr><td><code>agent.question</code></td><td>{ agentId, question, options? }</td><td>When agent pauses for input</td><td>Command bar alert, chat overlay</td></tr>
<tr><td><code>validation.result</code></td><td>{ targetId, targetType, state, details }</td><td>On validation completion</td><td>Workspace section badges, memo citations</td></tr>
<tr><td><code>filing.new</code></td><td>{ companyId, formType, title }</td><td>On new SEC filing detected</td><td>Filing Watch, toast, Thesis Alerts</td></tr>
<tr><td><code>alert.thesis</code></td><td>{ companyId, impact, summary }</td><td>When thesis impact detected</td><td>Thesis Alerts, toast</td></tr>
<tr><td><code>export.complete</code></td><td>{ exportId, format, fileSize }</td><td>On export finish</td><td>Export Center, toast</td></tr>
</tbody>
</table>
<h4>Connection lifecycle</h4>
<ul>
<li><strong>Connect:</strong> On app startup, after the first <code>portfolio.get</code>. URL includes active company ID.</li>
<li><strong>Reconnect:</strong> Browser auto-reconnects on drop. Server replays missed events via <code>Last-Event-ID</code>.</li>
<li><strong>Company switch:</strong> Client closes the SSE connection and opens a new one with the new <code>companyId</code>.</li>
<li><strong>Client → server:</strong> All client-to-server messages go through RPC (§3.2), not through SSE. SSE is receive-only.</li>
</ul>
<h4>Agent chat streaming</h4>
<p>The <code>agent.chat</code> RPC method returns an SSE stream (not a JSON response). The server opens a new ephemeral SSE stream scoped to that chat session. The client renders <code>agent.output</code> events in the chat column as they arrive. The stream closes when the agent finishes its response.</p>
<!-- ═══════════════════════════════════════════ -->
<h3 id="state-shape">3.4 State Shape</h3>
<p>The state is split into server-authoritative data (SQLite) and client-only UI state (React/localStorage).</p>
<h4>Server-authoritative state (SQLite)</h4>
<pre>interface AppState {
portfolio: {
id: string;
name: string;
holdings: Holding[];
};
activeCompanyId: string | null;
companies: {
[companyId: string]: {
workspace: WorkspaceState;
model: ModelState;
memo: MemoState;
agents: AgentRunState[];
};
};
settings: ServerSettings;
exports: ExportRecord[];
}</pre>
<h4>Client-only state (React / localStorage)</h4>
<pre>interface ClientState {
activeScreen: 'home' | 'workspace' | 'model' | 'memo' | 'agents';
navCollapsed: Record&lt;string, boolean&gt;;
activeModelTab: string;
memoReviewMode: boolean;
agentFullscreenOpen: boolean;
agentCarouselIndex: number;
settingsOverlayOpen: boolean;
settingsPanel: SettingsPanel;
searchQuery: string;
searchOpen: boolean;
clientSettings: ClientSettings;
}</pre>
<h4><code>settingsPanel</code> enum <span class="v3-tag">v3</span></h4>
<pre>type SettingsPanel =
| 'agents'
| 'data-sources'
| 'export'
| 'display'
| 'keybindings'
| 'advanced';</pre>
<p>Six panels. The Keybindings panel (missing from prototype) lets the analyst remap every shortcut listed in §9.1. Each shortcut is stored as a key binding entry in <code>ClientSettings.keybindings</code>.</p>
<p><strong>Company selection is the root pivot.</strong> Choosing a holding refreshes the entire workspace — thesis, model, memo, agents all rebind to the new company. The client queries the server for the selected company's data on switch.</p>
<!-- ═══════════════════════════════════════════ -->
<h3 id="persistence">3.5 Persistence & Versioning</h3>
<ul>
<li><strong>Autosave:</strong> Debounced server writes — 2s after last keystroke for memo text, immediate for toggles, settings changes, and annotation actions. No explicit save button.</li>
<li><strong>Snapshots:</strong> The server stores a diff snapshot every 5 minutes during active editing sessions. Named checkpoints available via <code>⌘S</code>. Snapshots listed in Settings → Advanced → Version History with timestamp and optional label.</li>
<li><strong>Undo/redo:</strong> Standard <code>⌘Z</code> / <code>⌘⇧Z</code> within the current session. Undo stack is session-scoped (cleared on page reload). For cross-session rollback, use version history.</li>
</ul>
<h4>Version History UI <span class="v3-tag">v3</span></h4>
<p>Settings → Advanced → Version History shows a timeline of snapshots:</p>
<dl class="screen-ref">
<dt>Layout</dt>
<dd>Left column: scrollable list of snapshot entries (auto-saved + manual). Right column: diff preview of the selected snapshot vs. current state.</dd>
<dt>Snapshot entry</dt>
<dd>Timestamp (mono), optional label (editable), type badge ("Auto" muted, "Manual" accent), number of changes. Click to preview.</dd>
<dt>Diff preview</dt>
<dd>Side-by-side or unified diff view. Additions highlighted with <code>--green</code> background tint, deletions with <code>--red</code> background tint. Memo sections shown as prose diffs; model cells shown as value changes.</dd>
<dt>Actions</dt>
<dd>"Restore this version" (primary button, confirmation dialog), "Branch from here" (creates a new checkpoint from that state without discarding current), "Download snapshot" (JSON export).</dd>
</dl>
<!-- ═══════════════════════════════════════════ -->
<h3 id="first-run">3.6 First-Run Experience</h3>
<p>No onboarding wizard, no modals, no video. The first-run experience is:</p>
<ol>
<li><strong>Empty portfolio</strong> with a single centered card: "Add your first company" — a search/typeahead that queries a company database.</li>
<li>Once added, the <strong>workspace opens</strong> with all research sections in "loading" state (skeleton).</li>
<li>A <strong>3-step tooltip sequence</strong> highlights: Thesis → Model → Memo.</li>
<li>The first agent pipeline <strong>auto-queues</strong> (SEC Filings → Company Research → Financial Modeling).</li>
<li>The <strong>agent carousel</strong> in the command bar pulses to draw attention to the first running agent.</li>
</ol>
<!-- ═══════════════════════════════════════════ -->
<h2 id="screens">4. Screen Specifications</h2>
<!-- 4.1 HOME -->
<h3 id="screen-home">4.1 Home Dashboard</h3>
<p>The home screen is the analyst's morning briefing. It answers three questions at a glance: <em>How is my portfolio doing? What needs my attention today? What are my agents working on?</em></p>
<h4>Layout</h4>
<p>Full-width center column with two stacked zones:</p>
<ol>
<li><strong>Portfolio dashboard</strong> — a 3-column grid (holdings list | performance chart + metrics | sector treemap + factor tilt). The grid uses <code>gap: 1px</code> with <code>background: var(--border)</code> to create hairline separators between sections.</li>
<li><strong>Home grid</strong> — a 2-column CSS grid below the portfolio area containing: Earnings Calendar, Watchlist Alerts, Recent Reports & Exports (spans full width), Pending Reviews, Agent Status.</li>
</ol>
<h4>Portfolio dashboard components</h4>
<dl class="screen-ref">
<dt>Holdings List</dt>
<dd>Scrollable list (max-height 320px) with columns: ticker badge, price, change (green/red), weight percentage, actions menu (<code></code>). Ticker badges use inverted colors (<code>bg: var(--fg); color: var(--surface)</code>). The <code></code> button opens a dropdown with: Open workspace, Run full research, Export report, Set as active, Remove from portfolio.</dd>
<dt>Performance Chart</dt>
<dd>SVG area chart with 6-month price line, grid lines, labeled time axis, and a summary metrics grid (Total Value, Day P&L, Holdings count, Beta, Sharpe, Max Drawdown).</dd>
<dt>Sector Treemap</dt>
<dd>Nested flex rows sized by weight. Each cell shows sector name, percentage, and dollar value against a tinted background. Colors are categorical, not semantic.</dd>
<dt>Factor Tilt</dt>
<dd>Horizontal bar chart comparing portfolio factor exposure vs. S&P 500. Positive exposures tinted green, negative tinted red. Six factors: Value, Size, Momentum, Quality, Low Vol, Market Beta.</dd>
</dl>
<h4>Home grid cards</h4>
<dl class="screen-ref">
<dt>Earnings Calendar</dt>
<dd>Grouped by date with BMO/AMC timing tags. Compact row layout: ticker badge + quarter label + timing + countdown.</dd>
<dt>Watchlist Alerts</dt>
<dd>Event feed with thesis impact tags (Thesis +/Thesis /Filing). Click-through to relevant screen.</dd>
<dt>Reports Library</dt>
<dd>Full-width card grid (<code>repeat(auto-fill, minmax(200px, 1fr))</code>). Each report thumb shows type label, title, and status metadata.</dd>
<dt>Pending Reviews</dt>
<dd>Items awaiting human review with comment counts and unverified assumption flags.</dd>
<dt>Agent Status</dt>
<dd>Compact list of active agents with progress percentages and current actions. Links to full Agents screen.</dd>
</dl>
<!-- 4.2 WORKSPACE -->
<h3 id="screen-workspace">4.2 Company Workspace</h3>
<p>The workspace is a read-only research hub for a single company. It aggregates all structured data, reports, and source materials in one scrollable column with a collapsible section navigation sidebar.</p>
<h4>Left navigation</h4>
<p>240px wide, collapsible to 36px. Organized into four groups with dividers:</p>
<ol>
<li><strong>Research</strong> — Company Snapshot, Business Description, Segment / Revenue Build, Margin Build, Historical Financials, Three-Statement Model, Key KPIs, Management & Strategy.</li>
<li><strong>Analysis</strong> — Competitive Landscape, Peer Comparison, Valuation Analysis, Investment Thesis, Risks & Mitigants, Catalyst Tracker (§4.6).</li>
<li><strong>Monitoring</strong> — Earnings Monitor (§4.9), Filing Watch (§4.10), Thesis Alerts (§4.7).</li>
<li><strong>Library</strong> — Source Library, Export Center (§4.11).</li>
</ol>
<p>Active section indicated by a 3px accent bar on the left edge. Group headers use uppercase monospace at 10px.</p>
<h4>Center content structure</h4>
<dl class="screen-ref">
<dt>Header</dt>
<dd>Category eyebrow (monospace, uppercase, accent color) + company display name + one-line summary (founded, HQ, locations, employees). Followed by a metric grid (12 cells in a 4-column auto-fill layout).</dd>
<dt>Metric Grid</dt>
<dd><code>repeat(auto-fill, minmax(140px, 1fr))</code> with 1px gap. Each cell: uppercase label (mono), display-font value, optional sub-text. Used for market cap, EV, price, P/E, margins, revenue, etc.</dd>
<dt>Body Sections</dt>
<dd>H2 headings with top border divider. Prose paragraphs, data tables (spreadsheet class), and nested metric grids.</dd>
<dt>Reports Grid</dt>
<dd>Same card grid as home. Click-through to Memo or Model screens.</dd>
<dt>Source Materials</dt>
<dd>Stacked source cards showing type, title, metadata (pages, usage count). Types: SEC Filing, Earnings Transcript, Investor Presentation, Press Release.</dd>
</dl>
<!-- 4.3 MODEL -->
<h3 id="screen-model">4.3 Financial Model</h3>
<p>The model screen is a spreadsheet-grade financial analysis environment. It presents tabular data with monospace numerics, explicit actual-vs-forecast distinction, and a tabbed navigation system for different model views.</p>
<h4>Toolbar</h4>
<p>Top toolbar with a <code>&lt;select&gt;</code> dropdown for model tabs: Revenue Build, Income Statement, Balance Sheet, Cash Flow Statement, Margin Build, Tax Build, LIFO/FIFO Conversion, Scenario Analysis, Charts. Right-aligned actions: Import Excel, AI Assist, Export to Excel.</p>
<h4>Spreadsheet specification</h4>
<dl class="screen-ref">
<dt>Font</dt>
<dd><code>12px/1.4 var(--font-mono)</code> for all cells. Headers: <code>10px</code> uppercase mono.</dd>
<dt>Layout</dt>
<dd>Full-width table. First column is text-aligned left with font-weight 500. All other columns right-aligned. Minimum column width 200px for row labels.</dd>
<dt>Actuals vs Forecasts</dt>
<dd>Actual periods use default <code>var(--fg)</code>. Forecast columns get class <code>.forecast</code>: <code>color: var(--muted); font-style: italic</code>.</dd>
<dt>Hierarchy</dt>
<dd>Indent rows (growth percentages) use <code>.indent</code> with <code>padding-left: 24px</code>. Total rows use <code>.total</code> with a <code>1px solid var(--fg)</code> top border and bold weight.</dd>
<dt>Hover</dt>
<dd>Row background shifts to <code>oklch(96% 0.008 80)</code> on hover.</dd>
<dt>Sticky headers</dt>
<dd>Table headers (<code>&lt;th&gt;</code>) are <code>position: sticky; top: 0</code> with <code>background: var(--surface)</code>.</dd>
</dl>
<h4>Model sub-tabs content</h4>
<p>All model tabs share the same spreadsheet specification above (monospace, hairline borders, sticky headers, actual vs. forecast distinction). Content per tab:</p>
<table class="spec-table">
<thead><tr><th>Tab</th><th>Row structure</th><th>Key metrics</th></tr></thead>
<tbody>
<tr><td>Revenue Build</td><td>Segments → growth rates → total</td><td>Revenue by segment, YoY growth, blended rate</td></tr>
<tr><td>Income Statement</td><td>Revenue → COGS → gross profit → OpEx → EBIT → net income</td><td>Margins at each level, tax rate</td></tr>
<tr><td>Balance Sheet</td><td>Assets (current + non-current) → Liabilities → Equity</td><td>Working capital, D/E, net debt</td></tr>
<tr><td>Cash Flow</td><td>Operating → Investing → Financing → FCF</td><td>FCF conversion, capex ratio</td></tr>
<tr><td>Margin Build</td><td>Gross → operating → EBITDA → net</td><td>Margin drivers, expansion/compression</td></tr>
<tr><td>Tax Build</td><td>Effective rate → deferred → cash taxes</td><td>Tax rate bridge, NOL utilization</td></tr>
<tr><td>LIFO/FIFO</td><td>Inventory layers → COGS impact → reserve adjustment</td><td>LIFO reserve, COGS delta</td></tr>
<tr><td>Scenario Analysis</td><td>Bear / Base / Bull columns side-by-side</td><td>Key variable sensitivities, probability weights</td></tr>
<tr><td>Charts</td><td>2-column grid of chart cards</td><td>Visual versions of the tab data</td></tr>
</tbody>
</table>
<!-- 4.4 MEMO -->
<h3 id="screen-memo">4.4 Memo Editor</h3>
<p>The memo screen is a structured writing environment for investment memos, IC memos, and research reports. It combines a content-editable center column with an outline sidebar and a review tools panel.</p>
<h4>Three-column layout</h4>
<table class="spec-table">
<thead>
<tr><th>Column</th><th>Width</th><th>Contents</th></tr>
</thead>
<tbody>
<tr><td>Left nav</td><td>220px (collapsible to 36px)</td><td>Memo outline — hierarchical list of H2 sections and sub-items. Active section indicated by accent bar + bold.</td></tr>
<tr><td>Center</td><td>720px max-width, centered</td><td>Sticky toolbar (status tag, Review Mode toggle, Export PDF, Publish) + memo blocks with eyebrows, headings, body text, citations, and AI edit suggestions.</td></tr>
<tr><td>Right panel</td><td>300px</td><td>Review Tools (Highlight, Comment, Draw Box), Citations list, Review Status per section, Comments thread.</td></tr>
</tbody>
</table>
<h4>Memo block structure</h4>
<p>Each memo block (<code>.memo-block</code>) contains: an uppercase eyebrow in accent color, an H3 heading, body prose with inline citations (<code>.citation</code> — accent-colored mono brackets), and optional AI edit suggestions (yellow-tinted sidebar with Accept/Reject/Revise buttons).</p>
<h4>Memo sections (8 total) <span class="v3-tag">v3</span></h4>
<p>The memo outline includes these sections, all following the same <code>.memo-block</code> structure (eyebrow + H3 + body + citations). The prototype currently shows 4 sections (Thesis, Key Drivers, Variant Perception, Valuation). The remaining 4 sections — Business Quality, Financial Summary, Risks & Mitigants, Catalysts — should be added:</p>
<table class="spec-table">
<thead><tr><th>Section</th><th>Content</th><th>Primary agent</th><th>Prototype status</th></tr></thead>
<tbody>
<tr><td>Investment Thesis</td><td>Core bull case, time horizon, target price</td><td><code>mw</code></td><td>Built ✓</td></tr>
<tr><td>Key Drivers</td><td>Top 35 revenue/earnings drivers with quantified impact</td><td><code>mw</code> + <code>fm</code></td><td>Built ✓</td></tr>
<tr><td>Variant Perception</td><td>Where the analyst disagrees with consensus and why</td><td><code>mw</code> + <code>rt</code></td><td>Built ✓</td></tr>
<tr><td>Valuation</td><td>Methodology, multiples, DCF summary, sensitivity table</td><td><code>va</code></td><td>Built ✓</td></tr>
<tr><td>Business Quality</td><td>Moat analysis, ROIC trends, capital allocation, management quality</td><td><code>cr</code></td><td>Missing — add block</td></tr>
<tr><td>Financial Summary</td><td>Key financials table, trend analysis, margin drivers</td><td><code>fm</code></td><td>Missing — add block</td></tr>
<tr><td>Risks & Mitigants</td><td>Top risks with severity/likelihood and mitigation strategies</td><td><code>rk</code> + <code>rt</code></td><td>Missing — add block</td></tr>
<tr><td>Catalysts</td><td>Near-term events that could re-rate the stock</td><td><code>mn</code></td><td>Missing — add block</td></tr>
</tbody>
</table>
<h4>Review mode</h4>
<p>Toggling "Review Mode" activates an annotation system with four tools:</p>
<ol>
<li><strong>Highlight</strong> — wraps selected text in <code>.highlight-annotation</code> with a tinted background and accent bottom border. Hover shows tooltip with reviewer comment.</li>
<li><strong>Comment</strong> — same as highlight but prompts for a comment string stored in <code>data-comment</code>.</li>
<li><strong>Strikethrough</strong> — applies <code>text-decoration: line-through</code> with red color at 60% opacity.</li>
<li><strong>Draw Box</strong> — enters crosshair mode to draw a red bounding box with a "Review needed" label over any content area.</li>
</ol>
<p>The annotation toolbar is positioned absolutely relative to the center column and appears at the selection position on mouseup.</p>
<h4>Citations</h4>
<p>Inline citations use numbered brackets linked to source cards in the right panel. Each source card shows: type (SEC Filing, Earnings Transcript, Analyst Report), title, page reference, and verification status (Verified / Unverified).</p>
<blockquote>The memo editor is not a general-purpose rich text editor. It is a structured document with defined sections, each mapped to an outline entry. This constraint enables the AI agents to write, review, and iterate on specific sections independently.</blockquote>
<!-- 4.5 AGENTS -->
<h3 id="screen-agents">4.5 Agent Orchestration</h3>
<p>The agents screen is the control center for MosaicIQ's 14-agent research pipeline. It visualizes dependencies, progress, and real-time output across all agents.</p>
<h4>Layout</h4>
<p>Four stacked zones:</p>
<ol>
<li><strong>Toolbar</strong> — agent count, running/queued status indicators, Pause All and Run Full Research buttons.</li>
<li><strong>Dependency tree</strong> — an SVG diagram showing the full agent pipeline with color-coded status. Two main pipelines (Research, Competitive & Risk) plus cross-cutting agents (Source Verification, Model QA, Monitoring, Export).</li>
<li><strong>Agent grid</strong><code>repeat(auto-fill, minmax(210px, 1fr))</code> tiles. Each tile shows: icon badge, agent name, role description, status dot + label, progress bar. Hover reveals action buttons (Pause, Inspect, Start, Configure). Clicking opens the detail panel.</li>
<li><strong>Activity feed</strong> — a scrollable log of timestamped agent events. Each entry: time, agent name (accent color), message.</li>
</ol>
<h4>Agent detail panel</h4>
<p>A 360px slide-in panel on the right showing: agent name + status header, current task description, upstream inputs (dependency tags), downstream outputs, execution log (timestamped entries), and action buttons (Pause, Restart, Full Screen).</p>
<h4>Fullscreen overlay</h4>
<p>Clicking "Full Screen" opens a fixed overlay with: tab bar for switching between running agents, a live output column (rendered agent output — tables, text, charts), and an execution trace column (numbered steps with labels and detail text). A chat input at the bottom allows sending interrupts or instructions to the active agent.</p>
<h4>Command bar</h4>
<p>Persistent across all screens. Left section: agent carousel showing the currently active agent with a pulsing dot, name, current action, and prev/next navigation (320px). Right section: chat prompt with <code>&gt;</code> icon and <code>⌘K</code> keyboard hint. Clicking the carousel opens the agent fullscreen overlay.</p>
<!-- 4.6 CATALYST TRACKER -->
<h3 id="screen-catalyst">4.6 Catalyst Tracker</h3>
<p>A workspace sub-section under "Analysis". Lists upcoming catalysts for the active company. Data comes from the Monitoring agent (<code>mn</code>) and is validated by Source Verification (<code>sv</code>).</p>
<h4>Layout</h4>
<p>Full-width table with columns:</p>
<table class="spec-table">
<thead><tr><th>Column</th><th>Spec</th></tr></thead>
<tbody>
<tr><td>Date</td><td>Monospace, sorted ascending</td></tr>
<tr><td>Event</td><td>Earnings, FDA decision, product launch, regulatory filing</td></tr>
<tr><td>Expected Impact</td><td>High / Med / Low pill (confidence pill spec from §6.2)</td></tr>
<tr><td>Thesis Relevance</td><td>Alignment tag: <code style="color:var(--green)">supports</code> / <code style="color:var(--red)">challenges</code> / <code style="color:var(--muted)">neutral</code></td></tr>
<tr><td>Source</td><td>Filing reference link</td></tr>
</tbody>
</table>
<h4>HTML structure <span class="v3-tag">v3</span></h4>
<pre>&lt;table class="spreadsheet"&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Date&lt;/th&gt;
&lt;th&gt;Event&lt;/th&gt;
&lt;th&gt;Impact&lt;/th&gt;
&lt;th&gt;Thesis&lt;/th&gt;
&lt;th&gt;Source&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td class="mono"&gt;Jun 5, 2026&lt;/td&gt;
&lt;td&gt;Q3 FY25 Earnings&lt;/td&gt;
&lt;td&gt;&lt;span class="tag red"&gt;High&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span style="color:var(--green)"&gt;supports&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span class="citation"&gt;[4]&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;!-- more rows --&gt;
&lt;/tbody&gt;
&lt;/table&gt;</pre>
<!-- 4.7 THESIS ALERTS -->
<h3 id="screen-thesis-alerts">4.7 Thesis Alerts</h3>
<p>A workspace sub-section under "Monitoring" + a card on the home dashboard. Generated by the Monitoring agent (<code>mn</code>), triggered by: new filings, price moves exceeding threshold, earnings results vs. expectations, peer events.</p>
<h4>Layout</h4>
<p>Event feed with: timestamp, event type icon (filing, price move, earnings surprise), description, and impact assessment. Each alert has:</p>
<ul>
<li>A thesis impact tag: <code style="color:var(--green)">[Thesis +]</code> (green), <code style="color:var(--red)">[Thesis ]</code> (red), <code style="color:var(--muted)">[Neutral]</code> (muted)</li>
<li>"Review" button → navigates to the relevant memo section or workspace section</li>
<li>Status: <code>New</code> (accent) → <code>Reviewed</code> (muted) after analyst clicks</li>
</ul>
<h4>HTML structure <span class="v3-tag">v3</span></h4>
<pre>&lt;div class="alert-feed"&gt;
&lt;div class="card-row"&gt;
&lt;span class="row-meta"&gt;2h ago&lt;/span&gt;
&lt;span class="row-name"&gt;Q2 FY25 earnings beat (+7.5% comp)&lt;/span&gt;
&lt;span class="tag green"&gt;Thesis +&lt;/span&gt;
&lt;button class="btn sm"&gt;Review&lt;/button&gt;
&lt;span class="tag accent"&gt;New&lt;/span&gt;
&lt;/div&gt;
&lt;/div&gt;</pre>
<!-- 4.8 RISK REGISTER -->
<h3 id="screen-risk-register">4.8 Risk Register</h3>
<p>A workspace sub-section under "Analysis". Structured list of identified risks. Populated by the Risk agent (<code>rk</code>) and stress-tested by Red Team (<code>rt</code>). Analysts can add manual risk entries via <code>risk.add</code> RPC.</p>
<h4>Layout</h4>
<table class="spec-table">
<thead><tr><th>Column</th><th>Spec</th></tr></thead>
<tbody>
<tr><td>Risk</td><td>Short description, left-aligned, font-weight 500</td></tr>
<tr><td>Category</td><td>Business / Financial / Competitive / Regulatory / ESG — tag</td></tr>
<tr><td>Severity</td><td>High / Med / Low pill</td></tr>
<tr><td>Likelihood</td><td>High / Med / Low pill</td></tr>
<tr><td>Mitigation</td><td>One-line description, muted text</td></tr>
<tr><td>Status</td><td>Open / Mitigated / Accepted — tag</td></tr>
</tbody>
</table>
<p>Sorted by severity × likelihood. Top risks surface in the memo's "Risks & Mitigants" section automatically.</p>
<h4>HTML structure <span class="v3-tag">v3</span></h4>
<pre>&lt;table class="spreadsheet"&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th style="min-width:200px"&gt;Risk&lt;/th&gt;
&lt;th&gt;Category&lt;/th&gt;
&lt;th&gt;Severity&lt;/th&gt;
&lt;th&gt;Likelihood&lt;/th&gt;
&lt;th&gt;Mitigation&lt;/th&gt;
&lt;th&gt;Status&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Amazon enters warehouse club segment&lt;/td&gt;
&lt;td&gt;&lt;span class="tag"&gt;Competitive&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span class="tag red"&gt;High&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span class="tag"&gt;Low&lt;/span&gt;&lt;/td&gt;
&lt;td style="color:var(--muted)"&gt;Costco's 93% renewal rate creates switching cost&lt;/td&gt;
&lt;td&gt;&lt;span class="tag"&gt;Open&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;</pre>
<!-- 4.9 EARNINGS MONITOR -->
<h3 id="screen-earnings">4.9 Earnings Monitor</h3>
<p>A workspace sub-section under "Monitoring" + the home dashboard calendar card. Beyond the calendar on home, the workspace section shows:</p>
<ul>
<li>Next 4 quarters with expected report dates</li>
<li>Last 4 quarters with actual vs. expected results</li>
<li>Consensus estimates grid (revenue, EPS, EBITDA)</li>
<li>Post-earnings analysis links (if Earnings Call agent has run)</li>
</ul>
<h4>HTML structure <span class="v3-tag">v3</span></h4>
<pre>&lt;div class="earnings-monitor"&gt;
&lt;!-- Two stacked grids --&gt;
&lt;div class="metric-grid" style="grid-template-columns:repeat(auto-fill,minmax(160px,1fr))"&gt;
&lt;div class="metric-cell"&gt;
&lt;div class="label"&gt;Next Report&lt;/div&gt;
&lt;div class="value"&gt;Jun 5, 2026&lt;/div&gt;
&lt;div class="sub"&gt;Q3 FY25 · BMO&lt;/div&gt;
&lt;/div&gt;
&lt;!-- more cells --&gt;
&lt;/div&gt;
&lt;h2 class="section"&gt;Consensus Estimates&lt;/h2&gt;
&lt;table class="spreadsheet"&gt;
&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Metric&lt;/th&gt;&lt;th&gt;Q2 FY25A&lt;/th&gt;&lt;th&gt;Q3 FY25E&lt;/th&gt;&lt;th&gt;FY25E&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;Revenue&lt;/td&gt;&lt;td&gt;$62.5B&lt;/td&gt;&lt;td class="forecast"&gt;$63.8B&lt;/td&gt;&lt;td class="forecast"&gt;$242.3B&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;EPS&lt;/td&gt;&lt;td&gt;$4.28&lt;/td&gt;&lt;td class="forecast"&gt;$4.35&lt;/td&gt;&lt;td class="forecast"&gt;$16.82&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;</pre>
<!-- 4.10 FILING WATCH -->
<h3 id="screen-filing-watch">4.10 Filing Watch</h3>
<p>A workspace sub-section under "Monitoring". Tracks new SEC filings for the active company. Real-time monitoring via the Monitoring agent (<code>mn</code>), which checks for new filings and queues the SEC Filings agent (<code>sf</code>) automatically.</p>
<h4>Layout</h4>
<p>Feed of filing cards. Each card: form type (10-K, 10-Q, 8-K, etc.) as a tag, filing date, title, key changes summary (produced by SEC Filings agent), and "Review" button → opens the filing in the Source Library with agent annotations.</p>
<h4>HTML structure <span class="v3-tag">v3</span></h4>
<pre>&lt;div class="filing-feed"&gt;
&lt;div class="source-card"&gt;
&lt;div class="src-type"&gt;10-K Annual Report&lt;/div&gt;
&lt;div class="src-title"&gt;Annual Report FY2024&lt;/div&gt;
&lt;div class="src-meta"&gt;Filed Oct 2024 &lt;/div&gt;
&lt;div style="font-size:11px;color:var(--fg);margin-top:4px"&gt;
Key changes: Updated segment reporting, new warehouse commitments ($2.1B)
&lt;/div&gt;
&lt;button class="btn sm" style="margin-top:6px"&gt;Review&lt;/button&gt;
&lt;/div&gt;
&lt;/div&gt;</pre>
<!-- 4.11 EXPORT CENTER -->
<h3 id="screen-export">4.11 Export Center</h3>
<p>A workspace sub-section under "Library" + accessible from memo/model toolbars. Export is handled by the Export agent (<code>ex</code>).</p>
<h4>Layout</h4>
<p>Grid of export records. Each record: type icon, title, format tag (PDF/Excel/PPT), timestamp, file size, status (processing/complete/failed), "Download" action.</p>
<h4>Trigger points</h4>
<ul>
<li>Memo toolbar → "Export PDF"</li>
<li>Model toolbar → "Export to Excel"</li>
<li>Agent orchestration → "Export full report" (comprehensive package)</li>
<li>Any screen → <code>⌘E</code> keyboard shortcut opens an export quick-action menu</li>
</ul>
<h4>HTML structure <span class="v3-tag">v3</span></h4>
<pre>&lt;div class="report-grid"&gt;
&lt;div class="report-thumb"&gt;
&lt;div class="rt-type"&gt;Excel&lt;/div&gt;
&lt;div class="rt-title"&gt;COST Model — Revenue Build FY25FY27&lt;/div&gt;
&lt;div class="rt-meta"&gt;2.4 MB · Complete&lt;/div&gt;
&lt;button class="btn sm" style="margin-top:6px"&gt;Download&lt;/button&gt;
&lt;/div&gt;
&lt;/div&gt;</pre>
<!-- ═══════════════════════════════════════════ -->
<h2 id="components">5. Component Library</h2>
<h3>Topbar</h3>
<table class="spec-table">
<thead><tr><th>Element</th><th>Spec</th></tr></thead>
<tbody>
<tr><td>Logo</td><td><code>font: 600 15px var(--font-display)</code>, clickable → Home. Text reads "MosaicIQ".</td></tr>
<tr><td>Ticker group</td><td>Hidden on Home screen. Shows: ticker badge (inverted), company name, price + change.</td></tr>
<tr><td>Screen tabs</td><td>Segmented control with 1px border. Active tab: <code>bg: var(--fg); color: var(--surface)</code>.</td></tr>
<tr><td>Search</td><td>180px input, <code>var(--bg)</code> background, mono placeholder. See §8.5 for search behavior.</td></tr>
<tr><td>Settings</td><td>28×28px icon button, opens settings overlay.</td></tr>
<tr><td>Avatar</td><td>28px circle, mono initials, hover → accent border.</td></tr>
</tbody>
</table>
<h3>Tags</h3>
<table class="spec-table">
<thead><tr><th>Variant</th><th>Spec</th></tr></thead>
<tbody>
<tr><td>Default</td><td><code>border: 1px solid var(--border); color: var(--muted)</code></td></tr>
<tr><td>Accent</td><td><code>border-color: var(--accent); color: var(--accent)</code></td></tr>
<tr><td>Green</td><td><code>border-color: var(--green); color: var(--green)</code></td></tr>
<tr><td>Red</td><td><code>border-color: var(--red); color: var(--red)</code></td></tr>
</tbody>
</table>
<p>All tags use <code>font: 500 10px var(--font-mono); text-transform: uppercase; letter-spacing: 0.06em; padding: 3px 8px</code>.</p>
<h3>Buttons</h3>
<table class="spec-table">
<thead><tr><th>Variant</th><th>Spec</th></tr></thead>
<tbody>
<tr><td>Default</td><td><code>bg: var(--surface); border: 1px solid var(--border); color: var(--fg)</code>. Hover: <code>bg: var(--bg)</code>.</td></tr>
<tr><td>Primary</td><td><code>bg: var(--fg); color: var(--surface); border-color: var(--fg)</code>. Hover: <code>opacity: 0.9</code>.</td></tr>
<tr><td>Small</td><td><code>font-size: 10px; padding: 3px 8px</code></td></tr>
</tbody>
</table>
<p>Base spec: <code>font: 500 12px var(--font-body); padding: 7px 14px; border-radius: 2px</code>.</p>
<h3>Source cards</h3>
<p><code>border: 1px solid var(--border); padding: 10px 12px; background: var(--surface)</code>. Three rows: type (9px uppercase mono, accent color), title (12px body), meta (10.5px muted).</p>
<h3>Report thumbs</h3>
<p><code>border: 1px solid var(--border); background: var(--bg); padding: 14px</code>. Hover → <code>border-color: var(--muted)</code>. Three rows: type label (accent mono), title (bold body), metadata (muted).</p>
<h3>Left navigation</h3>
<p>240px wide (220px for memo variant). Collapsible to 36px via <code>.collapsed</code> class which hides all content except a 28px expand button. Transition: <code>width 0.2s ease, opacity 0.2s ease</code>. Active items: 3px accent bar on left edge, bold text. Group headers: 10px uppercase mono, muted.</p>
<h3>Overlays</h3>
<p>Full-screen overlays for Settings, Agent fullscreen, and Profile editor. Fixed position, <code>z-index: 500</code> (600 for profile modal). Agent overlay includes a tab bar for switching between agents, with colored status dots.</p>
<h3>Dropdown / select</h3>
<table class="spec-table">
<thead><tr><th>Property</th><th>Spec</th></tr></thead>
<tbody>
<tr><td>Trigger</td><td><code>font: 400 12px var(--font-body); padding: 7px 28px 7px 12px; border: 1px solid var(--border); background: var(--surface); border-radius: 2px; appearance: none</code> with custom chevron via <code>background-image: url("data:image/svg+xml,...")</code></td></tr>
<tr><td>Panel</td><td><code>position: absolute; background: var(--surface); border: 1px solid var(--border); border-radius: 2px; box-shadow: 0 4px 12px oklch(0% 0 0 / 0.08)</code></td></tr>
<tr><td>Items</td><td><code>padding: 7px 12px; font-size: 12px</code>. Hover: <code>background: var(--bg)</code>. Active: <code>font-weight: 600; color: var(--accent)</code></td></tr>
</tbody>
</table>
<p><strong>Implementation note <span class="v3-tag">v3</span>:</strong> The prototype currently uses native <code>&lt;select&gt;</code> elements as an interim solution. The custom dropdown above is the target implementation. Both approaches should produce visually consistent results — the custom dropdown adds keyboard navigation, type-ahead filtering, and full visual control.</p>
<h3>Tooltips</h3>
<table class="spec-table">
<thead><tr><th>Property</th><th>Spec</th></tr></thead>
<tbody>
<tr><td>Trigger</td><td>Hover with 300ms delay (show) / 100ms delay (hide). Or focus.</td></tr>
<tr><td>Position</td><td>Auto-positioned above trigger, flipping below if no space.</td></tr>
<tr><td>Style</td><td><code>background: var(--fg); color: var(--surface); font: 400 11px/1.4 var(--font-body); padding: 4px 8px; border-radius: 2px; max-width: 240px</code></td></tr>
<tr><td>Arrow</td><td>6px CSS triangle, matching background color.</td></tr>
<tr><td>Content</td><td>Plain text only. No rich content — use the detail panel or a popover for that.</td></tr>
</tbody>
</table>
<h3>Actions menu on holdings</h3>
<p>The <code></code> button on each holding row opens a dropdown menu (same dropdown component spec as above):</p>
<table class="spec-table">
<thead><tr><th>Item</th><th>Action</th></tr></thead>
<tbody>
<tr><td>Open workspace</td><td>Switches to workspace screen for this company</td></tr>
<tr><td>Run full research</td><td>Queues all agents for this company</td></tr>
<tr><td>Export report</td><td>Opens export quick-action for this company</td></tr>
<tr><td>Set as active</td><td>Makes this the active company (topbar ticker updates)</td></tr>
<tr><td>Remove</td><td>Confirmation dialog → removes holding</td></tr>
</tbody>
</table>
<h3>Annotation components <span class="v3-tag">v3</span></h3>
<p>These classes are used by the review mode annotation system (§4.4) and in <code>app.js</code> but were previously undocumented in the component library:</p>
<table class="spec-table">
<thead><tr><th>Class</th><th>CSS Spec</th><th>Usage</th></tr></thead>
<tbody>
<tr><td><code>.highlight-annotation</code></td><td><code>background: oklch(90% 0.06 80); border-bottom: 2px solid var(--accent); cursor: pointer</code>. <code>:hover::after</code> shows tooltip via <code>data-comment</code> attribute.</td><td>Text highlight + comment annotations</td></tr>
<tr><td><code>.box-annotation</code></td><td><code>position: absolute; border: 2px solid var(--red); pointer-events: none; z-index: 200</code></td><td>Bounding box drawn via crosshair mode</td></tr>
<tr><td><code>.box-label</code></td><td><code>position: absolute; top: -20px; left: 0; background: var(--red); color: var(--surface); font: 500 9px var(--font-mono); padding: 2px 6px</code></td><td>Label inside box annotations</td></tr>
<tr><td><code>.comment-pin</code></td><td><code>display: inline-flex; width: 16px; height: 16px; background: var(--accent); color: var(--surface); border-radius: 50%; font: 600 8px var(--font-mono)</code></td><td>Inline comment indicator</td></tr>
<tr><td><code>.annotation-toolbar</code></td><td><code>position: absolute; background: var(--fg); color: var(--surface); border-radius: 2px; padding: 4px; z-index: 300; box-shadow: 0 4px 12px oklch(20% 0.02 60 / 0.2)</code></td><td>Floating toolbar on text selection</td></tr>
</tbody>
</table>
<h3>Focus ring (reusable pattern) <span class="v3-tag">v3</span></h3>
<p>All interactive elements (buttons, links, inputs, nav items) must show a visible focus ring for keyboard navigation:</p>
<pre>:focus-visible {
outline: 2px solid var(--accent);
outline-offset: 2px;
}
:focus:not(:focus-visible) {
outline: none;
}</pre>
<p>This ensures keyboard users see the ring while mouse clicks do not produce a visible outline. The ring uses the accent color at 2px with a 2px offset from the element boundary.</p>
<h3>Shimmer animation (skeleton loading) <span class="v3-tag">v3</span></h3>
<pre>@keyframes shimmer {
0% { background-position: -200% 0; }
100% { background-position: 200% 0; }
}
.skeleton {
background: linear-gradient(
90deg,
oklch(92% 0.008 80) 25%,
oklch(96% 0.008 80) 50%,
oklch(92% 0.008 80) 75%
);
background-size: 200% 100%;
animation: shimmer 1.5s ease-in-out infinite;
border-radius: 2px;
}
[data-theme="dark"] .skeleton {
background: linear-gradient(
90deg,
oklch(28% 0.01 80) 25%,
oklch(22% 0.01 80) 50%,
oklch(28% 0.01 80) 75%
);
background-size: 200% 100%;
}</pre>
<!-- ═══════════════════════════════════════════ -->
<h2 id="agents">6. Agent System</h2>
<p>MosaicIQ's agent system is the primary content production pipeline. Agents ingest raw data (SEC filings, transcripts, market data), process it into structured outputs (financial models, company profiles, memos), and coordinate through a dependency graph. Critically, every agent is designed to feel like a <strong>collaborator</strong> — not a black box — surfacing its reasoning, flagging uncertainty, and deferring to the analyst at decision points.</p>
<h3>Agent roster (14 agents)</h3>
<table class="spec-table">
<thead><tr><th>ID</th><th>Name</th><th>Role</th><th>Pipeline</th></tr></thead>
<tbody>
<tr><td><code>cr</code></td><td>Company Research</td><td>Structured profiles from filings, transcripts, external data</td><td>Main Research</td></tr>
<tr><td><code>sf</code></td><td>SEC Filings</td><td>Segment data, KPIs, risk factors, accounting policies</td><td>Main Research</td></tr>
<tr><td><code>fm</code></td><td>Financial Modeling</td><td>Revenue builds, margin models, three-statement frameworks</td><td>Main Research</td></tr>
<tr><td><code>ec</code></td><td>Earnings Call</td><td>Management tone, guidance, KPIs, Q&A themes</td><td>Competitive & Risk</td></tr>
<tr><td><code>ci</code></td><td>Competitive Intel</td><td>Peer analysis, market positioning, competitive threats</td><td>Competitive & Risk</td></tr>
<tr><td><code>va</code></td><td>Valuation</td><td>DCF, trading comps, scenario analysis, multiples</td><td>Main Research</td></tr>
<tr><td><code>rk</code></td><td>Risk</td><td>Business, financial, competitive, regulatory risks</td><td>Competitive & Risk</td></tr>
<tr><td><code>mw</code></td><td>Memo Writing</td><td>Investment memos, research reports, IC memos</td><td>Main Research</td></tr>
<tr><td><code>pa</code></td><td>Presentation</td><td>IC presentation drafts, slide outlines, exhibits</td><td>Main Research</td></tr>
<tr><td><code>mn</code></td><td>Monitoring</td><td>Filing alerts, thesis changes, earnings events</td><td>Cross-cutting</td></tr>
<tr><td><code>sv</code></td><td>Source Verification</td><td>Citation checking, source reliability, cross-referencing</td><td>Cross-cutting</td></tr>
<tr><td><code>rt</code></td><td>Red Team</td><td>Thesis challenges, assumption stress-testing, bear cases</td><td>Competitive & Risk</td></tr>
<tr><td><code>ex</code></td><td>Export</td><td>PDF, Excel, PowerPoint export pipelines</td><td>Cross-cutting</td></tr>
<tr><td><code>qa</code></td><td>Model QA</td><td>Formula auditing, balance sheet checks, sanity tests</td><td>Cross-cutting</td></tr>
</tbody>
</table>
<h3>Dependency graph</h3>
<p>Agents execute in a directed acyclic graph with two main pipelines and a cross-cutting layer:</p>
<div class="diagram">
<h4>Fig 4. Agent Dependency Graph — with Research & Validation Cycle</h4>
<svg viewBox="0 0 680 400" xmlns="http://www.w3.org/2000/svg">
<!-- ══════ MAIN RESEARCH PIPELINE ══════ -->
<text x="10" y="14" style="font:500 8px var(--font-mono);text-transform:uppercase;letter-spacing:0.08em;fill:oklch(48% 0.015 60)">Main Research Pipeline</text>
<line x1="95" y1="34" x2="115" y2="34" stroke="oklch(89% 0.012 80)" stroke-width="1.5"/>
<line x1="255" y1="34" x2="275" y2="34" stroke="oklch(89% 0.012 80)" stroke-width="1.5"/>
<line x1="405" y1="52" x2="405" y2="74" stroke="oklch(89% 0.012 80)" stroke-width="1.5"/>
<line x1="465" y1="90" x2="485" y2="90" stroke="oklch(89% 0.012 80)" stroke-width="1.5"/>
<line x1="585" y1="90" x2="605" y2="90" stroke="oklch(89% 0.012 80)" stroke-width="1.5"/>
<rect x="10" y="22" width="85" height="24" rx="2" fill="oklch(99% 0.005 80)" stroke="oklch(52% 0.12 145)" stroke-width="1"/>
<text x="24" y="38" style="font:500 8px var(--font-mono);fill:oklch(52% 0.12 145)">SEC Filings</text>
<rect x="115" y="22" width="140" height="24" rx="2" fill="oklch(99% 0.005 80)" stroke="oklch(52% 0.12 145)" stroke-width="1"/>
<text x="124" y="38" style="font:500 8px var(--font-mono);fill:oklch(52% 0.12 145)">Company Research</text>
<rect x="275" y="22" width="130" height="24" rx="2" fill="oklch(99% 0.005 80)" stroke="oklch(58% 0.16 35)" stroke-width="1"/>
<text x="284" y="38" style="font:500 8px var(--font-mono);fill:oklch(58% 0.16 35)">Financial Modeling</text>
<rect x="345" y="78" width="120" height="24" rx="2" fill="oklch(99% 0.005 80)" stroke="oklch(70% 0.06 80)" stroke-width="1" stroke-dasharray="4 2"/>
<text x="359" y="94" style="font:500 8px var(--font-mono);fill:oklch(48% 0.015 60)">Valuation</text>
<rect x="485" y="78" width="100" height="24" rx="2" fill="oklch(99% 0.005 80)" stroke="oklch(89% 0.012 80)" stroke-width="1" stroke-dasharray="4 2"/>
<text x="497" y="94" style="font:500 8px var(--font-mono);fill:oklch(48% 0.015 60)">Memo Writing</text>
<rect x="605" y="78" width="65" height="24" rx="2" fill="oklch(99% 0.005 80)" stroke="oklch(89% 0.012 80)" stroke-width="1" stroke-dasharray="4 2"/>
<text x="613" y="94" style="font:500 8px var(--font-mono);fill:oklch(48% 0.015 60)">Presentation</text>
<text x="10" y="134" style="font:500 8px var(--font-mono);text-transform:uppercase;letter-spacing:0.08em;fill:oklch(48% 0.015 60)">Competitive & Risk</text>
<line x1="100" y1="148" x2="120" y2="148" stroke="oklch(89% 0.012 80)" stroke-width="1.5"/>
<line x1="260" y1="148" x2="280" y2="148" stroke="oklch(89% 0.012 80)" stroke-width="1.5"/>
<line x1="380" y1="148" x2="400" y2="148" stroke="oklch(89% 0.012 80)" stroke-width="1.5"/>
<rect x="10" y="136" width="90" height="24" rx="2" fill="oklch(99% 0.005 80)" stroke="oklch(58% 0.16 35)" stroke-width="1"/>
<text x="22" y="152" style="font:500 8px var(--font-mono);fill:oklch(58% 0.16 35)">Earnings Call</text>
<rect x="120" y="136" width="140" height="24" rx="2" fill="oklch(99% 0.005 80)" stroke="oklch(70% 0.06 80)" stroke-width="1"/>
<text x="133" y="152" style="font:500 8px var(--font-mono);fill:oklch(48% 0.015 60)">Competitive Intel</text>
<rect x="280" y="136" width="100" height="24" rx="2" fill="oklch(99% 0.005 80)" stroke="oklch(89% 0.012 80)" stroke-width="1" stroke-dasharray="4 2"/>
<text x="295" y="152" style="font:500 8px var(--font-mono);fill:oklch(48% 0.015 60)">Risk</text>
<rect x="4" y="178" width="672" height="56" rx="2" fill="oklch(96% 0.006 80)" stroke="oklch(89% 0.012 80)" stroke-width="1" stroke-dasharray="6 3"/>
<text x="12" y="192" style="font:600 7px var(--font-mono);text-transform:uppercase;letter-spacing:0.1em;fill:oklch(48% 0.015 60)">Validation Layer</text>
<line x1="345" y1="102" x2="345" y2="178" stroke="oklch(89% 0.012 80)" stroke-width="1" stroke-dasharray="3 2"/>
<line x1="535" y1="102" x2="535" y2="178" stroke="oklch(89% 0.012 80)" stroke-width="1" stroke-dasharray="3 2"/>
<line x1="330" y1="160" x2="330" y2="178" stroke="oklch(89% 0.012 80)" stroke-width="1" stroke-dasharray="3 2"/>
<rect x="20" y="200" width="130" height="24" rx="2" fill="oklch(99% 0.005 80)" stroke="oklch(52% 0.12 145)" stroke-width="1.5"/>
<text x="30" y="216" style="font:500 8px var(--font-mono);fill:oklch(52% 0.12 145)">sv · Source Verify</text>
<rect x="165" y="200" width="120" height="24" rx="2" fill="oklch(99% 0.005 80)" stroke="oklch(52% 0.12 145)" stroke-width="1.5"/>
<text x="178" y="216" style="font:500 8px var(--font-mono);fill:oklch(52% 0.12 145)">qa · Model QA</text>
<rect x="300" y="200" width="130" height="24" rx="2" fill="oklch(99% 0.005 80)" stroke="oklch(58% 0.16 35)" stroke-width="1.5"/>
<text x="310" y="216" style="font:600 8px var(--font-mono);fill:oklch(58% 0.16 35)">rt · Red Team ⚔</text>
<line x1="150" y1="212" x2="165" y2="212" stroke="oklch(52% 0.12 145)" stroke-width="1"/>
<line x1="285" y1="212" x2="300" y2="212" stroke="oklch(58% 0.16 35)" stroke-width="1"/>
<polygon points="163,209 163,215 170,212" fill="oklch(52% 0.12 145)"/>
<polygon points="298,209 298,215 305,212" fill="oklch(58% 0.16 35)"/>
<text x="445" y="208" style="font:400 7px var(--font-mono);fill:oklch(52% 0.12 145)">✓ pass</text>
<text x="445" y="220" style="font:400 7px var(--font-mono);fill:oklch(58% 0.16 35)">⚑ flagged</text>
<text x="510" y="208" style="font:400 7px var(--font-mono);fill:oklch(52% 0.14 25)">✗ fail → re-run</text>
<text x="510" y="220" style="font:400 7px var(--font-mono);fill:oklch(48% 0.015 60)">— unverified</text>
<line x1="225" y1="224" x2="225" y2="254" stroke="oklch(20% 0.02 60)" stroke-width="1.5"/>
<polygon points="222,252 228,252 225,258" fill="oklch(20% 0.02 60)"/>
<rect x="120" y="260" width="210" height="28" rx="2" fill="oklch(20% 0.02 60)" stroke="none"/>
<text x="140" y="278" style="font:600 9px var(--font-mono);fill:oklch(99% 0.005 80);letter-spacing:0.04em">◉ Analyst Checkpoint</text>
<text x="340" y="270" style="font:500 7px var(--font-mono);fill:oklch(52% 0.12 145)">accept → propagate</text>
<text x="340" y="282" style="font:500 7px var(--font-mono);fill:oklch(58% 0.16 35)">reject → back to agent</text>
<line x1="330" y1="266" x2="438" y2="266" stroke="oklch(52% 0.12 145)" stroke-width="1" stroke-dasharray="3 2"/>
<polygon points="436,263 436,269 442,266" fill="oklch(52% 0.12 145)"/>
<text x="450" y="254" style="font:500 8px var(--font-mono);text-transform:uppercase;letter-spacing:0.08em;fill:oklch(48% 0.015 60)">Cross-cutting</text>
<rect x="450" y="262" width="65" height="18" rx="2" fill="oklch(99% 0.005 80)" stroke="oklch(89% 0.012 80)" stroke-width="1" stroke-dasharray="3 2"/>
<text x="458" y="274" style="font:400 7px var(--font-mono);fill:oklch(48% 0.015 60)">Monitoring</text>
<rect x="525" y="262" width="55" height="18" rx="2" fill="oklch(99% 0.005 80)" stroke="oklch(89% 0.012 80)" stroke-width="1" stroke-dasharray="3 2"/>
<text x="532" y="274" style="font:400 7px var(--font-mono);fill:oklch(48% 0.015 60)">Export</text>
<path d="M 120,280 Q 2,280 2,90 Q 2,34 10,34" fill="none" stroke="oklch(58% 0.16 35)" stroke-width="1.5" stroke-dasharray="5 3"/>
<polygon points="8,31 8,37 14,34" fill="oklch(58% 0.16 35)"/>
<text x="6" y="175" style="font:500 7px var(--font-mono);fill:oklch(58% 0.16 35);writing-mode:tb;letter-spacing:0.06em">↻ RE-TRIGGER</text>
<line x1="185" y1="22" x2="185" y2="8" stroke="oklch(48% 0.015 60)" stroke-width="0.8" stroke-dasharray="2 2"/>
<text x="192" y="10" style="font:400 6px var(--font-mono);fill:oklch(48% 0.015 60)">asks analyst ↗</text>
<line x1="405" y1="78" x2="405" y2="66" stroke="oklch(48% 0.015 60)" stroke-width="0.8" stroke-dasharray="2 2"/>
<text x="412" y="68" style="font:400 6px var(--font-mono);fill:oklch(48% 0.015 60)">confidence: med</text>
<line x1="365" y1="200" x2="365" y2="162" stroke="oklch(58% 0.16 35)" stroke-width="1" stroke-dasharray="3 2"/>
<polygon points="362,164 368,164 365,160" fill="oklch(58% 0.16 35)"/>
<rect x="4" y="310" width="672" height="84" rx="2" fill="oklch(99% 0.005 80)" stroke="oklch(89% 0.012 80)" stroke-width="1"/>
<text x="14" y="326" style="font:600 7px var(--font-mono);text-transform:uppercase;letter-spacing:0.1em;fill:oklch(48% 0.015 60)">Legend</text>
<line x1="14" y1="340" x2="44" y2="340" stroke="oklch(89% 0.012 80)" stroke-width="1.5"/>
<text x="50" y="343" style="font:400 7px var(--font-mono);fill:oklch(48% 0.015 60)">Dependency</text>
<line x1="14" y1="354" x2="44" y2="354" stroke="oklch(89% 0.012 80)" stroke-width="1" stroke-dasharray="4 2"/>
<text x="50" y="357" style="font:400 7px var(--font-mono);fill:oklch(48% 0.015 60)">Optional / downstream</text>
<rect x="160" y="333" width="18" height="12" rx="1" fill="none" stroke="oklch(52% 0.12 145)" stroke-width="1.5"/>
<text x="184" y="343" style="font:400 7px var(--font-mono);fill:oklch(52% 0.12 145)">Verification agent</text>
<rect x="160" y="348" width="18" height="12" rx="1" fill="none" stroke="oklch(58% 0.16 35)" stroke-width="1.5"/>
<text x="184" y="357" style="font:400 7px var(--font-mono);fill:oklch(58% 0.16 35)">Adversarial agent</text>
<rect x="310" y="333" width="18" height="12" rx="1" fill="oklch(20% 0.02 60)" stroke="none"/>
<text x="334" y="343" style="font:400 7px var(--font-mono);fill:oklch(48% 0.015 60)">Human checkpoint</text>
<line x1="310" y1="354" x2="340" y2="354" stroke="oklch(58% 0.16 35)" stroke-width="1.5" stroke-dasharray="5 3"/>
<text x="346" y="357" style="font:400 7px var(--font-mono);fill:oklch(58% 0.16 35)">Auto re-trigger</text>
<line x1="310" y1="370" x2="340" y2="370" stroke="oklch(48% 0.015 60)" stroke-width="0.8" stroke-dasharray="2 2"/>
<text x="346" y="373" style="font:400 7px var(--font-mono);fill:oklch(48% 0.015 60)">Collaborator signal</text>
</svg>
</div>
<h3>Agent states</h3>
<table class="spec-table">
<thead><tr><th>State</th><th>Visual</th><th>Dot</th><th>Behavior</th></tr></thead>
<tbody>
<tr><td>Running</td><td>Accent border + pulse animation</td><td><code>var(--accent)</code></td><td>Progress bar fills, live output streams</td></tr>
<tr><td>Completed</td><td>Green border + check</td><td><code>var(--green)</code></td><td>100% bar, "View Output" action</td></tr>
<tr><td>Queued</td><td>Muted dashed border</td><td><code>oklch(70% 0.06 80)</code></td><td>0% bar, "Start Now" override</td></tr>
<tr><td>Idle</td><td>Default border</td><td><code>var(--muted)</code></td><td>"Start" action available</td></tr>
<tr><td>Paused</td><td>Amber dot, frozen progress bar</td><td><code>oklch(58% 0.16 35)</code></td><td>Agent is waiting for analyst input via chat</td></tr>
<tr><td>Failed</td><td>Red border + error icon</td><td><code>var(--red)</code></td><td>Error detail shown, "Retry" action</td></tr>
</tbody>
</table>
<h3 id="collaborator">6.1 Agents as Collaborators</h3>
<p>The agent system is built around a core interaction principle: <em>agents should feel like skilled co-analysts sitting across the desk</em>. This means they do not just produce outputs — they explain their reasoning, highlight where confidence is low, and actively invite the analyst into the loop.</p>
<h4>Collaboration behaviours</h4>
<ul>
<li><strong>Assumption surfacing.</strong> Every agent output includes a visible "Assumptions" section listing the key premises behind its analysis.</li>
<li><strong>Confidence signals.</strong> Agents annotate their findings with confidence levels (High / Medium / Low), displayed as inline pills next to the relevant data.</li>
<li><strong>Proactive questions.</strong> When an agent encounters ambiguity, it pauses and surfaces a question to the analyst via the command bar.</li>
<li><strong>Transparent reasoning traces.</strong> The fullscreen agent overlay shows a step-by-step execution log.</li>
<li><strong>Iterative handoff.</strong> Agents never present a final answer without offering a revision cycle.</li>
</ul>
<h3>Carousel</h3>
<p>The command bar carousel cycles through active agents (array <code>activeAgents</code>). It shows: pulsing dot, agent name, current action text, and a counter (<code>1 / 3</code>). Navigation via prev/next arrows or clicking the strip opens the fullscreen overlay.</p>
<!-- ═══════════════════════════════════════════ -->
<h3 id="agent-interaction">6.2 Agent Interaction Model</h3>
<p>All agent interaction flows through the command bar and fullscreen chat mode. The agent state is always visually clear in the supporting UI — the analyst should never wonder which agent they're interacting with or which part of the workspace is affected.</p>
<h4>Agent chat layout spec <span class="v3-tag">v3</span></h4>
<p>The chat interface renders in two contexts: the command bar (compact, single-line) and the fullscreen overlay (expanded, multi-line). Both use the same component structure:</p>
<table class="spec-table">
<thead><tr><th>Element</th><th>CSS Classes</th><th>Spec</th></tr></thead>
<tbody>
<tr><td>Agent message</td><td><code>.chat-msg.agent</code></td><td><code>max-width: 560px; background: var(--bg); border: 1px solid var(--border); padding: 12px 16px; border-radius: 2px</code>. Agent name in 9px mono uppercase accent above the bubble. Left-aligned.</td></tr>
<tr><td>Analyst message</td><td><code>.chat-msg.analyst</code></td><td>Same base spec. <code>background: var(--surface); border-color: var(--muted)</code>. Right-aligned with <code>margin-left: auto</code>.</td></tr>
<tr><td>Structured output (table)</td><td><code>.chat-msg .structured-table</code></td><td>Uses <code>.spreadsheet</code> class. Collapsed by default showing first 5 rows, with "Show all N rows" toggle.</td></tr>
<tr><td>Structured output (assumptions)</td><td><code>.chat-msg .assumption-list</code></td><td>Each assumption: checkbox (unchecked), text, confidence pill. "Accept all" / "Reject & re-run" buttons at bottom.</td></tr>
<tr><td>Quick actions</td><td><code>.chat-actions</code></td><td>Row of small buttons below agent message: "Accept assumptions", "Reject & re-run", "Show sources", "Revise". Flex row with <code>gap: 6px</code>.</td></tr>
<tr><td>Chat scroll area</td><td><code>.chat-scroll</code></td><td><code>flex: 1; overflow-y: auto; padding: 24px</code>. Messages stack vertically with <code>gap: 16px</code>. Max-width 680px, centered.</td></tr>
<tr><td>Chat input</td><td><code>.chat-input</code></td><td>Full-width input bar at bottom: <code>border-top: 1px solid var(--border); padding: 12px 24px</code>. Input field + send button. <code>⌘Enter</code> to send.</td></tr>
</tbody>
</table>
<h4>Agent alert in the command bar</h4>
<p>When an agent needs input, the command bar shows a persistent <code>!</code> alert. The <code>!</code> icon replaces the pulsing dot and turns amber, the agent name and a truncated question appear inline, and a "Respond" button opens the fullscreen overlay.</p>
<h4>Visual clarity: which agent is being impacted</h4>
<ol>
<li><strong>Command bar carousel</strong> — always shows the active agent name + current action.</li>
<li><strong>Agent grid tiles</strong> — the active/interacting agent gets a 2px accent border + subtle glow.</li>
<li><strong>Workspace sections</strong> — each section has a small agent attribution badge: <code>[cr · Company Research]</code> in 9px mono, muted.</li>
<li><strong>Fullscreen overlay</strong> — tab bar with status dots. Active tab has accent underline.</li>
<li><strong>Section flash</strong> — when an agent needs input, the relevant workspace section gets a subtle accent left-border flash (1s pulse, twice).</li>
</ol>
<h4>Confidence signals UI</h4>
<table class="spec-table">
<thead><tr><th>Level</th><th>Colors</th></tr></thead>
<tbody>
<tr><td style="color:oklch(52% 0.12 145)">High</td><td>bg <code>oklch(95% 0.04 145)</code>, border <code>oklch(52% 0.12 145)</code>, text <code>oklch(52% 0.12 145)</code></td></tr>
<tr><td style="color:oklch(58% 0.16 35)">Medium</td><td>bg <code>oklch(95% 0.04 60)</code>, border <code>oklch(58% 0.16 35)</code>, text <code>oklch(58% 0.16 35)</code></td></tr>
<tr><td style="color:oklch(52% 0.14 25)">Low</td><td>bg <code>oklch(95% 0.04 25)</code>, border <code>oklch(52% 0.14 25)</code>, text <code>oklch(52% 0.14 25)</code></td></tr>
</tbody>
</table>
<!-- ═══════════════════════════════════════════ -->
<h3 id="agent-configure">6.3 Agent Configuration Panel</h3>
<p>Clicking "Configure" on an agent tile opens a slide-in panel (360px, same as agent detail panel) with:</p>
<table class="spec-table">
<thead><tr><th>Section</th><th>Contents</th></tr></thead>
<tbody>
<tr><td>Identity</td><td>Agent name, role description, icon</td></tr>
<tr><td>Data sources</td><td>Toggle switches for: SEC filings, transcripts, market data, analyst reports, press releases</td></tr>
<tr><td>Model preferences</td><td>LLM model selection dropdown, temperature slider (01, step 0.1), max tokens input (number)</td></tr>
<tr><td>Scheduling</td><td>Auto-run triggers: "On new filing", "On earnings date", "Daily", "Manual only"</td></tr>
<tr><td>Output format</td><td>Agent-specific: memo outline vs prose, model forecast period, report sections</td></tr>
</tbody>
</table>
<h4>Config panel layout <span class="v3-tag">v3</span></h4>
<div class="diagram">
<h4>Fig 5. Agent Configuration Panel Wireframe</h4>
<svg viewBox="0 0 360 520" xmlns="http://www.w3.org/2000/svg">
<!-- Header -->
<rect x="0" y="0" width="360" height="48" fill="oklch(99% 0.005 80)" stroke="oklch(89% 0.012 80)" stroke-width="1"/>
<circle cx="24" cy="24" r="14" fill="oklch(96% 0.008 80)" stroke="oklch(89% 0.012 80)" stroke-width="1"/>
<text x="21" y="28" style="font:600 10px var(--font-mono);fill:oklch(48% 0.015 60)">fm</text>
<text x="48" y="20" style="font:600 13px var(--font-body);fill:oklch(20% 0.02 60)">Financial Modeling</text>
<text x="48" y="34" style="font:400 10px var(--font-mono);fill:oklch(48% 0.015 60);text-transform:uppercase;letter-spacing:0.04em">Idle · Configure</text>
<text x="336" y="24" style="font:400 16px var(--font-body);fill:oklch(48% 0.015 60)">×</text>
<!-- Section: Data Sources -->
<text x="16" y="72" style="font:600 9px var(--font-mono);text-transform:uppercase;letter-spacing:0.08em;fill:oklch(48% 0.015 60)">Data Sources</text>
<line x1="16" y1="78" x2="344" y2="78" stroke="oklch(89% 0.012 80)" stroke-width="1"/>
<!-- Toggle rows -->
<text x="16" y="100" style="font:400 12px var(--font-body);fill:oklch(20% 0.02 60)">SEC Filings</text>
<rect x="308" y="90" width="32" height="18" rx="9" fill="oklch(58% 0.16 35)"/>
<circle cx="326" cy="99" r="7" fill="oklch(99% 0.005 80)"/>
<text x="16" y="124" style="font:400 12px var(--font-body);fill:oklch(20% 0.02 60)">Earnings Transcripts</text>
<rect x="308" y="114" width="32" height="18" rx="9" fill="oklch(58% 0.16 35)"/>
<circle cx="326" cy="123" r="7" fill="oklch(99% 0.005 80)"/>
<text x="16" y="148" style="font:400 12px var(--font-body);fill:oklch(20% 0.02 60)">Market Data</text>
<rect x="308" y="138" width="32" height="18" rx="9" fill="oklch(89% 0.012 80)"/>
<circle cx="316" cy="147" r="7" fill="oklch(99% 0.005 80)"/>
<text x="16" y="172" style="font:400 12px var(--font-body);fill:oklch(20% 0.02 60)">Analyst Reports</text>
<rect x="308" y="162" width="32" height="18" rx="9" fill="oklch(89% 0.012 80)"/>
<circle cx="316" cy="171" r="7" fill="oklch(99% 0.005 80)"/>
<!-- Section: Model Preferences -->
<text x="16" y="204" style="font:600 9px var(--font-mono);text-transform:uppercase;letter-spacing:0.08em;fill:oklch(48% 0.015 60)">Model Preferences</text>
<line x1="16" y1="210" x2="344" y2="210" stroke="oklch(89% 0.012 80)" stroke-width="1"/>
<text x="16" y="232" style="font:400 11px var(--font-mono);fill:oklch(48% 0.015 60)">LLM Model</text>
<rect x="16" y="238" width="328" height="28" fill="oklch(99% 0.005 80)" stroke="oklch(89% 0.012 80)" stroke-width="1"/>
<text x="24" y="256" style="font:400 12px var(--font-body);fill:oklch(20% 0.02 60)">gpt-4o</text>
<text x="328" y="256" style="font:400 8px var(--font-mono);fill:oklch(48% 0.015 60)"></text>
<text x="16" y="286" style="font:400 11px var(--font-mono);fill:oklch(48% 0.015 60)">Temperature</text>
<rect x="16" y="294" width="328" height="4" fill="oklch(89% 0.012 80)" rx="2"/>
<rect x="16" y="294" width="66" height="4" fill="oklch(58% 0.16 35)" rx="2"/>
<circle cx="82" cy="296" r="6" fill="oklch(58% 0.16 35)"/>
<text x="332" y="290" style="font:500 10px var(--font-mono);fill:oklch(20% 0.02 60)">0.2</text>
<text x="16" y="326" style="font:400 11px var(--font-mono);fill:oklch(48% 0.015 60)">Max Tokens</text>
<rect x="260" y="314" width="84" height="24" fill="oklch(99% 0.005 80)" stroke="oklch(89% 0.012 80)" stroke-width="1"/>
<text x="268" y="330" style="font:400 12px var(--font-body);fill:oklch(20% 0.02 60)">4096</text>
<!-- Section: Scheduling -->
<text x="16" y="362" style="font:600 9px var(--font-mono);text-transform:uppercase;letter-spacing:0.08em;fill:oklch(48% 0.015 60)">Scheduling</text>
<line x1="16" y1="368" x2="344" y2="368" stroke="oklch(89% 0.012 80)" stroke-width="1"/>
<rect x="16" y="378" width="156" height="24" fill="oklch(20% 0.02 60)"/>
<text x="30" y="394" style="font:500 10px var(--font-mono);fill:oklch(99% 0.005 80)">On new filing</text>
<rect x="180" y="378" width="84" height="24" fill="oklch(99% 0.005 80)" stroke="oklch(89% 0.012 80)" stroke-width="1"/>
<text x="194" y="394" style="font:500 10px var(--font-mono);fill:oklch(48% 0.015 60)">Daily</text>
<rect x="272" y="378" width="84" height="24" fill="oklch(99% 0.005 80)" stroke="oklch(89% 0.012 80)" stroke-width="1"/>
<text x="286" y="394" style="font:500 10px var(--font-mono);fill:oklch(48% 0.015 60)">Manual</text>
<!-- Section: Output Format -->
<text x="16" y="426" style="font:600 9px var(--font-mono);text-transform:uppercase;letter-spacing:0.08em;fill:oklch(48% 0.015 60)">Output Format</text>
<line x1="16" y1="432" x2="344" y2="432" stroke="oklch(89% 0.012 80)" stroke-width="1"/>
<text x="16" y="454" style="font:400 11px var(--font-mono);fill:oklch(48% 0.015 60)">Forecast Period</text>
<rect x="260" y="442" width="84" height="24" fill="oklch(99% 0.005 80)" stroke="oklch(89% 0.012 80)" stroke-width="1"/>
<text x="268" y="458" style="font:400 12px var(--font-body);fill:oklch(20% 0.02 60)">3 years</text>
<text x="332" y="458" style="font:400 8px var(--font-mono);fill:oklch(48% 0.015 60)"></text>
<text x="16" y="484" style="font:400 11px var(--font-mono);fill:oklch(48% 0.015 60)">Granularity</text>
<rect x="260" y="472" width="84" height="24" fill="oklch(99% 0.005 80)" stroke="oklch(89% 0.012 80)" stroke-width="1"/>
<text x="268" y="488" style="font:400 12px var(--font-body);fill:oklch(20% 0.02 60)">Quarterly</text>
<text x="332" y="488" style="font:400 8px var(--font-mono);fill:oklch(48% 0.015 60)"></text>
<!-- Footer -->
<rect x="0" y="504" width="360" height="16" fill="oklch(97% 0.012 80)" stroke="oklch(89% 0.012 80)" stroke-width="1"/>
<text x="16" y="515" style="font:400 9px var(--font-mono);fill:oklch(48% 0.015 60)">Changes take effect on next agent run</text>
</svg>
</div>
<!-- ═══════════════════════════════════════════ -->
<h2 id="validation">7. Research & Validation Cycle</h2>
<p>MosaicIQ enforces a structured <strong>research → validate → iterate</strong> cycle across the entire agent pipeline. No output reaches the analyst's desk without passing through at least one validation checkpoint.</p>
<h4>Cycle stages</h4>
<ol>
<li><strong>Research.</strong> Agents gather data, build models, and draft analysis. Each agent marks its output with confidence levels and assumptions.</li>
<li><strong>Cross-verification.</strong> Source Verification (<code>sv</code>) checks citations. Model QA (<code>qa</code>) audits formulas and sanity thresholds. Both return pass/fail reports.</li>
<li><strong>Adversarial stress-test.</strong> Red Team (<code>rt</code>) challenges the research — building bear cases, stress-testing assumptions, surfacing contradictions.</li>
<li><strong>Analyst checkpoint.</strong> Reviewed output presented with: verified (green ✓), flagged (amber ⚑), unverified (grey —). Analyst accepts, revises, or rejects.</li>
<li><strong>Propagation.</strong> Approved sections propagate downstream. Upstream changes auto re-trigger affected agents.</li>
</ol>
<h3 id="validation-flow">7.1 Validation Flow Diagram <span class="v3-tag">v3</span></h3>
<div class="diagram">
<h4>Fig 6. Validation Failure Flow — Sequence Diagram</h4>
<svg viewBox="0 0 680 320" xmlns="http://www.w3.org/2000/svg">
<!-- Column headers -->
<text x="40" y="16" style="font:600 9px var(--font-mono);text-transform:uppercase;letter-spacing:0.08em;fill:oklch(48% 0.015 60)">Research Agent</text>
<text x="230" y="16" style="font:600 9px var(--font-mono);text-transform:uppercase;letter-spacing:0.08em;fill:oklch(52% 0.12 145)">sv / qa</text>
<text x="380" y="16" style="font:600 9px var(--font-mono);text-transform:uppercase;letter-spacing:0.08em;fill:oklch(58% 0.16 35)">rt · Red Team</text>
<text x="530" y="16" style="font:600 9px var(--font-mono);text-transform:uppercase;letter-spacing:0.08em;fill:oklch(20% 0.02 60)">Analyst</text>
<!-- Vertical lifelines -->
<line x1="80" y1="24" x2="80" y2="310" stroke="oklch(89% 0.012 80)" stroke-width="1" stroke-dasharray="4 2"/>
<line x1="280" y1="24" x2="280" y2="310" stroke="oklch(89% 0.012 80)" stroke-width="1" stroke-dasharray="4 2"/>
<line x1="440" y1="24" x2="440" y2="310" stroke="oklch(89% 0.012 80)" stroke-width="1" stroke-dasharray="4 2"/>
<line x1="580" y1="24" x2="580" y2="310" stroke="oklch(89% 0.012 80)" stroke-width="1" stroke-dasharray="4 2"/>
<!-- 1. Research produces output -->
<rect x="50" y="32" width="60" height="20" fill="oklch(58% 0.16 35)" rx="2"/>
<text x="56" y="46" style="font:500 7px var(--font-mono);fill:oklch(99% 0.005 80)">output</text>
<!-- Arrow to sv/qa -->
<line x1="110" y1="42" x2="250" y2="42" stroke="oklch(52% 0.12 145)" stroke-width="1.5"/>
<polygon points="248,39 248,45 255,42" fill="oklch(52% 0.12 145)"/>
<text x="140" y="38" style="font:400 7px var(--font-mono);fill:oklch(48% 0.015 60)">validate()</text>
<!-- sv/qa result: pass -->
<rect x="250" y="54" width="60" height="20" fill="oklch(52% 0.12 145)" rx="2"/>
<text x="256" y="68" style="font:500 7px var(--font-mono);fill:oklch(99% 0.005 80)">✓ pass</text>
<!-- Arrow to rt -->
<line x1="310" y1="64" x2="410" y2="64" stroke="oklch(58% 0.16 35)" stroke-width="1.5"/>
<polygon points="408,61 408,67 415,64" fill="oklch(58% 0.16 35)"/>
<text x="330" y="60" style="font:400 7px var(--font-mono);fill:oklch(48% 0.015 60)">stress-test()</text>
<!-- rt result: flagged -->
<rect x="410" y="76" width="60" height="20" fill="oklch(58% 0.16 35)" rx="2"/>
<text x="416" y="90" style="font:500 7px var(--font-mono);fill:oklch(99% 0.005 80)">⚑ flagged</text>
<!-- Arrow to analyst -->
<line x1="470" y1="86" x2="550" y2="86" stroke="oklch(20% 0.02 60)" stroke-width="1.5"/>
<polygon points="548,83 548,89 555,86" fill="oklch(20% 0.02 60)"/>
<text x="482" y="82" style="font:400 7px var(--font-mono);fill:oklch(48% 0.015 60)">present</text>
<!-- Analyst: reject -->
<rect x="550" y="98" width="60" height="20" fill="oklch(52% 0.14 25)" rx="2"/>
<text x="556" y="112" style="font:500 7px var(--font-mono);fill:oklch(99% 0.005 80)">✗ reject</text>
<!-- Arrow back to research agent -->
<line x1="550" y1="108" x2="110" y2="140" stroke="oklch(52% 0.14 25)" stroke-width="1.5" stroke-dasharray="4 2"/>
<polygon points="108,137 108,143 115,140" fill="oklch(52% 0.14 25)"/>
<text x="280" y="128" style="font:500 7px var(--font-mono);fill:oklch(52% 0.14 25)">re-research specific items</text>
<!-- Research re-runs -->
<rect x="50" y="150" width="60" height="20" fill="oklch(58% 0.16 35)" rx="2" opacity="0.7"/>
<text x="52" y="164" style="font:500 7px var(--font-mono);fill:oklch(99% 0.005 80)">re-run</text>
<!-- Arrow to sv/qa (second pass) -->
<line x1="110" y1="160" x2="250" y2="160" stroke="oklch(52% 0.12 145)" stroke-width="1.5" opacity="0.7"/>
<polygon points="248,157 248,163 255,160" fill="oklch(52% 0.12 145)"/>
<!-- sv/qa pass (second) -->
<rect x="250" y="172" width="60" height="20" fill="oklch(52% 0.12 145)" rx="2" opacity="0.7"/>
<text x="256" y="186" style="font:500 7px var(--font-mono);fill:oklch(99% 0.005 80)">✓ pass</text>
<!-- Arrow to rt (second) -->
<line x1="310" y1="182" x2="410" y2="182" stroke="oklch(58% 0.16 35)" stroke-width="1.5" opacity="0.7"/>
<polygon points="408,179 408,185 415,182" fill="oklch(58% 0.16 35)"/>
<!-- rt pass -->
<rect x="410" y="194" width="60" height="20" fill="oklch(52% 0.12 145)" rx="2" opacity="0.7"/>
<text x="416" y="208" style="font:500 7px var(--font-mono);fill:oklch(99% 0.005 80)">✓ pass</text>
<!-- Arrow to analyst -->
<line x1="470" y1="204" x2="550" y2="204" stroke="oklch(20% 0.02 60)" stroke-width="1.5"/>
<polygon points="548,201 548,207 555,204" fill="oklch(20% 0.02 60)"/>
<!-- Analyst: accept -->
<rect x="550" y="216" width="60" height="20" fill="oklch(52% 0.12 145)" rx="2"/>
<text x="556" y="230" style="font:500 7px var(--font-mono);fill:oklch(99% 0.005 80)">✓ accept</text>
<!-- Propagate downstream -->
<line x1="580" y1="236" x2="580" y2="280" stroke="oklch(52% 0.12 145)" stroke-width="1.5"/>
<polygon points="577,278 583,278 580,284" fill="oklch(52% 0.12 145)"/>
<text x="588" y="260" style="font:400 7px var(--font-mono);fill:oklch(52% 0.12 145)">propagate</text>
<text x="588" y="270" style="font:400 7px var(--font-mono);fill:oklch(48% 0.015 60)">downstream</text>
<!-- Duration annotations -->
<text x="16" y="296" style="font:400 8px var(--font-mono);fill:oklch(48% 0.015 60)">Typical cycle:</text>
<text x="16" y="308" style="font:400 8px var(--font-mono);fill:oklch(52% 0.12 145)">sv/qa: 1030s · rt: 3090s · analyst: manual</text>
</svg>
</div>
<h4>Validation states in the UI</h4>
<table class="spec-table">
<thead><tr><th>State</th><th>Indicator</th><th>Meaning</th></tr></thead>
<tbody>
<tr><td><span style="color:var(--green)">✓ Verified</span></td><td>Green check icon + muted border</td><td>Source Verification and Model QA have both passed.</td></tr>
<tr><td><span style="color:var(--accent)">⚑ Flagged</span></td><td>Amber flag icon + accent border</td><td>Adversarial review raised concerns; analyst action recommended.</td></tr>
<tr><td><span style="color:var(--muted)">— Unverified</span></td><td>Grey dash + dashed border</td><td>Not yet through the validation cycle.</td></tr>
<tr><td><span style="color:var(--red)">✗ Failed</span></td><td>Red cross + red border</td><td>Verification failed. Routed back to the producing agent.</td></tr>
</tbody>
</table>
<h4>Validation on actual content</h4>
<ul>
<li><strong>Workspace sections:</strong> Badge cluster in section header showing aggregate state: <code>✓ 3 passed</code>, <code>⚑ 1 flagged</code>.</li>
<li><strong>Model cells:</strong> Failed QA cells get a subtle red left-border with tooltip.</li>
<li><strong>Memo paragraphs:</strong> Inline validation follows the citation — <code>[12] ✓</code> or <code>[12] ⚑</code>.</li>
<li><strong>Accept/Reject/Revise:</strong> Right-click context menu on validated elements.</li>
</ul>
<blockquote>The validation cycle is the reason MosaicIQ's agents can be trusted: they do not just work fast, they work <em>checked</em>.</blockquote>
<hr/>
<!-- ═══════════════════════════════════════════ -->
<h2 id="interactive-states">8. Interactive States</h2>
<p>Every data-bound surface in MosaicIQ has defined rendering modes for loading, empty, error, and success states.</p>
<h3 id="loading-states">8.1 Loading & Skeleton States</h3>
<p>Every data-bound surface has three rendering modes:</p>
<h4>Skeleton (first load)</h4>
<p>Grey placeholder blocks matching the layout shape of the real content. Uses the <code>.skeleton</code> class with the shimmer animation defined in §5. Skeleton shapes per surface:</p>
<table class="spec-table">
<thead><tr><th>Surface</th><th>Skeleton HTML pattern</th></tr></thead>
<tbody>
<tr><td>Metric grid cells</td><td><code>&lt;div class="metric-cell"&gt;&lt;div class="skeleton" style="height:12px;width:60px"&gt;&lt;/div&gt;&lt;div class="skeleton" style="height:20px;width:80px;margin-top:4px"&gt;&lt;/div&gt;&lt;/div&gt;</code></td></tr>
<tr><td>Spreadsheet rows</td><td><code>&lt;tr&gt;&lt;td colspan="N"&gt;&lt;div class="skeleton" style="height:14px"&gt;&lt;/div&gt;&lt;/td&gt;&lt;/tr&gt;</code> × 6 rows</td></tr>
<tr><td>Memo blocks</td><td><code>&lt;div class="memo-block"&gt;&lt;div class="skeleton" style="height:10px;width:120px"&gt;&lt;/div&gt;&lt;div class="skeleton" style="height:18px;width:80%;margin-top:8px"&gt;&lt;/div&gt;&lt;div class="skeleton" style="height:14px;width:100%;margin-top:6px"&gt;&lt;/div&gt;&lt;/div&gt;</code></td></tr>
<tr><td>Agent tiles</td><td><code>&lt;div class="agent-tile"&gt;&lt;div class="skeleton" style="height:28px;width:28px"&gt;&lt;/div&gt;&lt;div class="skeleton" style="height:13px;width:70%;margin-top:10px"&gt;&lt;/div&gt;&lt;div class="skeleton" style="height:10px;width:50%;margin-top:6px"&gt;&lt;/div&gt;&lt;/div&gt;</code></td></tr>
<tr><td>Holdings list</td><td><code>&lt;div class="pd-holding-row"&gt;&lt;div class="skeleton" style="height:16px;width:36px"&gt;&lt;/div&gt;&lt;div class="skeleton" style="height:12px;width:50%"&gt;&lt;/div&gt;&lt;div class="skeleton" style="height:12px;width:40px"&gt;&lt;/div&gt;&lt;/div&gt;</code> × 4</td></tr>
</tbody>
</table>
<h4>Stale (background refresh)</h4>
<p>Existing content shown at <code>opacity: 0.7</code> with a thin accent bar at the top of the panel. No skeleton overlay.</p>
<h4>Live (loaded)</h4>
<p>Normal rendering. No visual treatment.</p>
<h3 id="empty-states">8.2 Empty States</h3>
<p>Centered flex column with an icon/illustration, a title, a one-line description, and an optional action button:</p>
<table class="spec-table">
<thead><tr><th>Surface</th><th>Icon</th><th>Title</th><th>Description</th><th>Action</th></tr></thead>
<tbody>
<tr><td>Holdings list</td><td>Briefcase outline</td><td>No holdings yet</td><td>Add a company to begin research</td><td>"Add company"</td></tr>
<tr><td>Model spreadsheet</td><td>Grid icon</td><td>No model data</td><td>Run the Financial Modeling agent to build</td><td>"Run agent"</td></tr>
<tr><td>Memo body</td><td>Document icon</td><td>No memo drafted</td><td>Run the Memo Writing agent or start writing</td><td>"Start writing"</td></tr>
<tr><td>Agent grid</td><td></td><td>Show all 14 agents in "Idle" state</td><td></td><td>"Run Full Research"</td></tr>
<tr><td>Reports library</td><td>Folder icon</td><td>No reports yet</td><td>Reports appear as agents complete</td><td></td></tr>
<tr><td>Search results</td><td>Magnifier icon</td><td>No results</td><td>Try a different search term</td><td></td></tr>
<tr><td>Activity feed</td><td>Clock icon</td><td>No activity yet</td><td>Agent events will appear here</td><td></td></tr>
</tbody>
</table>
<h3 id="error-states">8.3 Error States</h3>
<p>A bordered panel with semantic coloring and an icon. The error alert replaces the content area of the failed section (not the whole screen). Dismissible via <code>×</code> in the corner.</p>
<table class="spec-table">
<thead><tr><th>Error type</th><th>Variant</th><th>Icon</th><th>Title</th><th>Behavior</th></tr></thead>
<tbody>
<tr><td>Agent failure</td><td><code>error</code> (red border, red-tinted bg)</td><td><code>!</code> circle</td><td>"[Agent name] failed"</td><td>Shows error detail + "Retry" button</td></tr>
<tr><td>Network error (future)</td><td><code>warning</code> (amber border)</td><td>Triangle</td><td>"Connection lost"</td><td>Shows retrying status, auto-reconnects</td></tr>
<tr><td>Data load failure</td><td><code>error</code></td><td><code>!</code> circle</td><td>"Failed to load [section]"</td><td>Inline in the section, "Retry" button</td></tr>
<tr><td>Validation failure</td><td><code>warning</code></td><td>Flag</td><td>"Validation flagged issues"</td><td>Links to the flagged items</td></tr>
<tr><td>Export failure</td><td><code>error</code></td><td><code>!</code> circle</td><td>"Export failed"</td><td>Shows error detail + "Retry"</td></tr>
</tbody>
</table>
<h3 id="toast-system">8.4 Toast & Notifications</h3>
<p>Stacked toasts anchored top-right, below topbar (<code>top: 56px</code>). Typed variants: <code>success</code>, <code>error</code>, <code>warning</code>, <code>info</code>, <code>loading</code>.</p>
<h4>Toast HTML structure <span class="v3-tag">v3</span></h4>
<pre>&lt;div class="toast-container"&gt;
&lt;div class="toast toast-success"&gt;
&lt;div class="toast-icon"&gt;&lt;/div&gt;
&lt;div class="toast-body"&gt;
&lt;div class="toast-title"&gt;SEC Filings Agent completed&lt;/div&gt;
&lt;div class="toast-desc"&gt;COST 10-K analysis ready&lt;/div&gt;
&lt;/div&gt;
&lt;button class="toast-action"&gt;View output&lt;/button&gt;
&lt;button class="toast-dismiss"&gt;×&lt;/button&gt;
&lt;/div&gt;
&lt;/div&gt;</pre>
<h4>Toast CSS <span class="v3-tag">v3</span></h4>
<pre>.toast-container {
position: fixed; top: 56px; right: 16px; z-index: 1000;
display: flex; flex-direction: column; gap: 8px;
max-width: 380px; pointer-events: none;
}
.toast {
display: flex; align-items: flex-start; gap: 10px;
background: var(--surface); border: 1px solid var(--border);
padding: 12px 14px; border-radius: 2px;
box-shadow: 0 4px 12px oklch(20% 0.02 60 / 0.08);
pointer-events: auto; animation: toast-in 200ms ease-out;
}
.toast.toast-success { border-left: 3px solid var(--green); }
.toast.toast-error { border-left: 3px solid var(--red); }
.toast.toast-warning { border-left: 3px solid var(--accent); }
.toast.toast-info { border-left: 3px solid var(--muted); }
.toast-icon {
font-size: 13px; flex-shrink: 0; margin-top: 1px;
}
.toast-success .toast-icon { color: var(--green); }
.toast-error .toast-icon { color: var(--red); }
.toast-warning .toast-icon { color: var(--accent); }
.toast-body { flex: 1; }
.toast-title { font: 500 12px/1.3 var(--font-body); }
.toast-desc { font-size: 11px; color: var(--muted); margin-top: 2px; }
.toast-action {
font: 500 10px var(--font-mono); color: var(--accent);
background: none; border: none; cursor: pointer;
text-transform: uppercase; letter-spacing: 0.04em;
white-space: nowrap; flex-shrink: 0;
}
.toast-dismiss {
background: none; border: none; color: var(--muted);
cursor: pointer; font-size: 14px; flex-shrink: 0;
padding: 0; line-height: 1;
}
@keyframes toast-in {
from { opacity: 0; transform: translateX(20px); }
to { opacity: 1; transform: translateX(0); }
}</pre>
<h4>Toast types in MosaicIQ</h4>
<table class="spec-table">
<thead><tr><th>Event</th><th>Type</th><th>Duration</th><th>Content</th></tr></thead>
<tbody>
<tr><td>Agent completes</td><td><code>success</code></td><td>4s auto-dismiss</td><td>"[Agent name] completed" + "View output"</td></tr>
<tr><td>Agent fails</td><td><code>error</code></td><td>Manual dismiss</td><td>"[Agent name] failed" + detail + "Retry"</td></tr>
<tr><td>Agent needs input</td><td><code>warning</code></td><td>Until dismissed</td><td>"[Agent name] needs input" + "Respond"</td></tr>
<tr><td>Thesis change</td><td><code>info</code></td><td>6s auto-dismiss</td><td>"Thesis change: [ticker] — [summary]" + "View"</td></tr>
<tr><td>Filing alert</td><td><code>info</code></td><td>6s auto-dismiss</td><td>"New filing: [form type] — [company]" + "Open"</td></tr>
<tr><td>Export complete</td><td><code>success</code></td><td>4s auto-dismiss</td><td>"[Type] exported" + "Open file"</td></tr>
<tr><td>Validation pass</td><td><code>success</code></td><td>3s auto-dismiss</td><td>"Source verification passed"</td></tr>
<tr><td>Validation flag</td><td><code>warning</code></td><td>Until dismissed</td><td>"[N] items flagged by [agent]" + "Review"</td></tr>
</tbody>
</table>
<h3 id="search-behavior">8.5 Search Behavior</h3>
<p>The search bar searches across four categories: <strong>companies</strong>, <strong>reports</strong>, <strong>files</strong>, and <strong>commands</strong>.</p>
<ol>
<li>Click search or press <code>⌘F</code> → search input expands, dropdown appears</li>
<li>Results stream in grouped by category</li>
<li>Each result row: type icon, title, secondary text, keyboard shortcut if applicable</li>
<li>Arrow keys navigate, Enter selects, Escape closes</li>
<li><code>⌘K</code> opens agent chat — separate from search</li>
</ol>
<hr/>
<!-- ═══════════════════════════════════════════ -->
<h2 id="interactions">9. Interaction Patterns</h2>
<h3 id="keyboard-shortcuts">9.1 Keyboard Shortcuts</h3>
<table class="spec-table">
<thead><tr><th>Shortcut</th><th>Action</th><th>Context</th></tr></thead>
<tbody>
<tr><td><code>⌘K</code></td><td>Open agent chat / command bar</td><td>Global</td></tr>
<tr><td><code>⌘F</code></td><td>Focus search bar</td><td>Global</td></tr>
<tr><td><code>⌘S</code></td><td>Create manual snapshot</td><td>Memo, Model</td></tr>
<tr><td><code>⌘E</code></td><td>Open export quick-action menu</td><td>Global</td></tr>
<tr><td><code>⌘\</code></td><td>Toggle left nav collapse</td><td>Workspace, Memo</td></tr>
<tr><td><code>⌘15</code></td><td>Switch to screen 15</td><td>Global</td></tr>
<tr><td><code>⌘.</code></td><td>Toggle settings overlay</td><td>Global</td></tr>
<tr><td><code>Escape</code></td><td>Close overlay / fullscreen / search</td><td>Overlay contexts</td></tr>
<tr><td><code>↑ / ↓</code></td><td>Navigate agent carousel, search results, model cells</td><td>Context-dependent</td></tr>
<tr><td><code>Enter</code></td><td>Select / confirm</td><td>Context-dependent</td></tr>
<tr><td><code>Tab</code></td><td>Move between memo sections</td><td>Memo</td></tr>
<tr><td><code>⌘Z</code></td><td>Undo last edit</td><td>Memo, Model</td></tr>
<tr><td><code>⌘⇧Z</code></td><td>Redo</td><td>Memo, Model</td></tr>
</tbody>
</table>
<p>All shortcuts configurable in Settings → Keybindings. The Keybindings panel (one of six settings tabs) shows every shortcut in an editable table: action name, current binding, "Record new shortcut" button. Bindings stored in <code>ClientSettings.keybindings</code>.</p>
<h3 id="dark-mode">9.2 Dark Mode</h3>
<p>Dark mode inverts the lightness axis while keeping hue/chroma relationships identical. Toggle via Settings → Display → Theme.</p>
<pre>:root[data-theme="dark"] {
--bg: oklch(15% 0.012 80);
--surface: oklch(18% 0.008 80);
--fg: oklch(92% 0.008 80);
--muted: oklch(60% 0.01 80);
--border: oklch(28% 0.01 80);
--accent: oklch(65% 0.16 35);
--green: oklch(60% 0.12 145);
--red: oklch(60% 0.14 25);
}</pre>
<h4>Dark mode component handling <span class="v3-tag">v3</span></h4>
<ul>
<li><strong>Typography stacks:</strong> Unchanged.</li>
<li><strong>Skeleton shimmer:</strong> Uses inverted lighter shade (see shimmer animation in §5).</li>
<li><strong>Charts:</strong> Grid lines use <code>var(--border)</code> (automatically darker). SVG area fills adjust opacity: reduce from 0.1 to 0.15 for visibility.</li>
<li><strong>Treemap cells:</strong> Reduce saturation by ~20% in dark mode to avoid vibrating against the dark background. Add a <code>data-theme</code> selector to override cell colors.</li>
<li><strong>Inverted elements:</strong> Ticker badges (<code>bg: var(--fg); color: var(--surface)</code>) auto-adapt through token swap — no component-level override needed.</li>
<li><strong>Shadows:</strong> In dark mode, shadows should use <code>oklch(0% 0 0 / 0.3)</code> instead of <code>oklch(20% 0.02 60 / 0.08)</code> for the same perceived depth.</li>
<li><strong>Toast borders:</strong> The left-border accent colors remain the same — they already have sufficient contrast on dark surfaces.</li>
<li><strong>Highlight annotations:</strong> Adjust to <code>background: oklch(28% 0.03 80)</code> so the tinted highlight is visible on the dark bg.</li>
</ul>
<h3 id="transitions">9.3 Screen Transitions</h3>
<table class="spec-table">
<thead><tr><th>Transition</th><th>Duration</th><th>Easing</th><th>Detail</th></tr></thead>
<tbody>
<tr><td>Screen switch</td><td>150ms</td><td><code>ease-out</code></td><td>Fade: old screen <code>opacity: 1 → 0</code>, new screen <code>opacity: 0 → 1</code>. No slide.</td></tr>
<tr><td>Nav collapse</td><td>200ms</td><td><code>ease</code></td><td>Width transition (existing)</td></tr>
<tr><td>Right panel slide-in</td><td>200ms</td><td><code>ease-out</code></td><td>Transform <code>translateX(100%) → translateX(0)</code></td></tr>
<tr><td>Overlay open</td><td>150ms</td><td><code>ease-out</code></td><td>Backdrop <code>opacity: 0 → 1</code>, content <code>scale(0.98) → scale(1)</code> + fade</td></tr>
<tr><td>Overlay close</td><td>100ms</td><td><code>ease-in</code></td><td>Reverse of open</td></tr>
<tr><td>Agent tile hover</td><td>100ms</td><td><code>linear</code></td><td>Action buttons fade in</td></tr>
</tbody>
</table>
<h4>Screen transition CSS <span class="v3-tag">v3</span></h4>
<pre>/* Screen switch — add to .screen */
.screen {
transition: opacity 150ms ease-out;
opacity: 0;
}
.screen.active {
opacity: 1;
}
/* Right panel slide-in */
.right-panel {
transform: translateX(100%);
transition: transform 200ms ease-out;
}
.right-panel.open {
transform: translateX(0);
}
/* Overlay */
.overlay {
opacity: 0;
transition: opacity 150ms ease-out;
}
.overlay .overlay-body {
transform: scale(0.98);
transition: transform 150ms ease-out;
}
.overlay.open {
opacity: 1;
}
.overlay.open .overlay-body {
transform: scale(1);
}</pre>
<h3 id="accessibility">9.4 Accessibility</h3>
<ul>
<li>All interactive elements have <code>role</code>, <code>aria-label</code>, and <code>tabindex</code> attributes</li>
<li>Screen switches announce via <code>aria-live="polite"</code> region</li>
<li>Agent status changes announced via <code>aria-live="assertive"</code> for failures, <code>polite</code> for completions</li>
<li>Focus management: screen switches move focus to the main content area; overlay opens trap focus within the overlay</li>
<li>Color contrast: all text meets WCAG AA</li>
<li>Keyboard-only navigation: all actions accessible without a mouse. Focus ring pattern defined in §5.</li>
</ul>
<h3 id="responsive">9.5 Responsive Behavior</h3>
<table class="spec-table">
<thead><tr><th>Breakpoint</th><th>Behavior</th></tr></thead>
<tbody>
<tr><td>≥1440px (primary)</td><td>Full layout: nav + center + panel</td></tr>
<tr><td>12801439px</td><td>Right panel becomes a slide-in overlay instead of persistent column</td></tr>
<tr><td>10241279px</td><td>Left nav starts collapsed (36px). Agent grid goes 2-column. Memo outline becomes a dropdown.</td></tr>
<tr><td>&lt;1024px</td><td>Not supported. App shows: "MosaicIQ requires a desktop display of 1024px or wider."</td></tr>
</tbody>
</table>
<h4>Responsive floor implementation <span class="v3-tag">v3</span></h4>
<pre>@media (max-width: 1023px) {
body > *:not(.floor-message) { display: none !important; }
body::before {
content: '';
display: block;
position: fixed; top: 0; left: 0; right: 0; bottom: 0;
background: var(--bg);
}
body::after {
content: 'MosaicIQ requires a desktop display of 1024px or wider.';
display: flex;
position: fixed; top: 0; left: 0; right: 0; bottom: 0;
align-items: center; justify-content: center;
font: 400 16px/1.5 var(--font-body);
color: var(--muted); text-align: center;
padding: 40px;
}
}</pre>
<h3 id="density">9.6 Data Density Settings</h3>
<p>Settings → Display → Data Density offers three options:</p>
<table class="spec-table">
<thead><tr><th>Property</th><th>Comfortable (default)</th><th>Compact</th><th>Dense</th></tr></thead>
<tbody>
<tr><td>Body font size</td><td>15px</td><td>14px</td><td>13px</td></tr>
<tr><td>Line height</td><td>1.7</td><td>1.5</td><td>1.4</td></tr>
<tr><td>Card padding</td><td>16px</td><td>12px</td><td>8px</td></tr>
<tr><td>Grid gap</td><td>1px</td><td>1px</td><td>0px</td></tr>
<tr><td>Table cell padding</td><td>8px 12px</td><td>6px 10px</td><td>4px 8px</td></tr>
<tr><td>Metric grid min-width</td><td>140px</td><td>120px</td><td>100px</td></tr>
<tr><td>Nav width (expanded)</td><td>240px</td><td>220px</td><td>200px</td></tr>
</tbody>
</table>
<hr/>
<div class="endnote">
This document describes the MosaicIQ equity research workspace. For implementation questions, refer to <code>index.html</code>, <code>styles.css</code>, and <code>app.js</code> in the project root. Version 3.0 addresses all 20 gaps identified in the v2 review: RPC surface (28 methods), real-time transport (SSE), naming convention, sub-screen HTML structures, agent chat layout, config panel wireframe, skeleton/empty/error/toast HTML+CSS, dark mode component handling, screen transition CSS, validation flow diagram, version history UI, annotation component docs, focus ring pattern, shimmer keyframes, settingsPanel enum, dropdown implementation note, responsive floor, missing memo sections, and keybindings panel spec.
</div>
<!-- ═══ RELATED ═══ -->
<div class="related">
<div class="related-card">
<div class="rc-thumb"></div>
<div class="rc-title">Design Tokens Reference</div>
<div class="rc-excerpt">Complete token catalogue with OKLch values, dark mode variants, and usage guidelines.</div>
<div class="rc-date">Tokens · v3.0</div>
</div>
<div class="related-card">
<div class="rc-thumb"></div>
<div class="rc-title">Agent Pipeline Specification</div>
<div class="rc-excerpt">Input/output contracts, interaction model, dependency resolution, and execution semantics.</div>
<div class="rc-date">Agents · v3.0</div>
</div>
<div class="related-card">
<div class="rc-thumb"></div>
<div class="rc-title">Interactive States Catalogue</div>
<div class="rc-excerpt">Skeleton, empty, error, and toast patterns for every data-bound surface.</div>
<div class="rc-date">States · v3.0</div>
</div>
</div>
</article>
</body>
</html>