From 62bdd79b3473262dde5fb0a90eab34fe7bf344fd Mon Sep 17 00:00:00 2001
From: Tyler Hoang <tyler@tylerhoang.xyz>
Date: Sun, 17 May 2026 13:07:40 -0700
Subject: 'UI Shell and General Architecture'

---
 frontend/app/page.tsx | 581 +++++++++++++++++++++++++-------------------------
 1 file changed, 296 insertions(+), 285 deletions(-)

(limited to 'frontend/app/page.tsx')

diff --git a/frontend/app/page.tsx b/frontend/app/page.tsx
index 0658929..f47d49a 100644
--- a/frontend/app/page.tsx
+++ b/frontend/app/page.tsx
@@ -1,27 +1,25 @@
 "use client";
 
-import { FormEvent, Suspense, useCallback, useEffect, useMemo, useRef, useState } from "react";
-import { BookmarkMinus, BookmarkPlus, Search } from "lucide-react";
+import { FormEvent, Suspense, startTransition, useCallback, useEffect, useMemo, useRef, useState } from "react";
+import Image from "next/image";
 import { useRouter, useSearchParams } from "next/navigation";
-import { PriceChart } from "@/components/PriceChart";
+import { AppShell } from "@/components/prism/AppShell";
+import { ChartCard } from "@/components/prism/ChartCard";
+import { KPIStrip } from "@/components/prism/KPIStrip";
+import { Sidebar } from "@/components/prism/Sidebar";
+import { TickerHeader } from "@/components/prism/TickerHeader";
+import { TopBar } from "@/components/prism/TopBar";
 import { ApiError, api } from "@/lib/api";
 import { deltaClass, fmtCurrency, fmtLarge, fmtNumber, fmtPct } from "@/lib/format";
+import { availableFieldSummary, buildKpis, marketClock, OVERVIEW_NAV_ITEMS, signalTone, sortIndices, unavailableFields } from "@/lib/overview";
 import type { HistoryPoint, MarketIndex, SearchResult, TickerOverview, WatchlistResponse } from "@/types/api";
 
-const PERIODS = [
-  { key: "1m", label: "1M" },
-  { key: "3m", label: "3M" },
-  { key: "6m", label: "6M" },
-  { key: "1y", label: "1Y" },
-  { key: "5y", label: "5Y" }
-];
-
 type LoadState = "idle" | "loading" | "ready" | "invalid" | "error";
 type ChartState = "idle" | "loading" | "ready" | "error";
 
 export default function OverviewPage() {
   return (
-    <Suspense fallback={<main className="shell"><section className="content"><div className="empty-panel">Loading Prism...</div></section></main>}>
+    <Suspense fallback={<LoadingShell />}>
       <OverviewClient />
     </Suspense>
   );
@@ -46,24 +44,37 @@ function OverviewClient() {
   const [chartState, setChartState] = useState<ChartState>("idle");
   const [chartError, setChartError] = useState<string | null>(null);
   const [watchlistError, setWatchlistError] = useState<string | null>(null);
+  const [clockSnapshot, setClockSnapshot] = useState(() => marketClock());
 
   const watchlistSymbols = useMemo(() => new Set(watchlist.items.map((item) => item.symbol)), [watchlist]);
   const isSaved = selectedTicker ? watchlistSymbols.has(selectedTicker) : false;
+  const marketCards = useMemo(() => sortIndices(market), [market]);
+  const kpis = useMemo(() => (overview ? buildKpis(overview) : []), [overview]);
+  const missingFields = useMemo(() => (overview ? unavailableFields(overview) : []), [overview]);
+
+  useEffect(() => {
+    const timer = window.setInterval(() => setClockSnapshot(marketClock()), 60_000);
+    return () => window.clearInterval(timer);
+  }, []);
 
-  const selectTicker = useCallback(
+  const navigateToTicker = useCallback(
     (symbol: string) => {
       const normalized = symbol.trim().toUpperCase();
       if (!normalized) return;
+
       setResults([]);
       setQuery("");
+      setOverview(null);
+      setHistory([]);
       setOverviewError(null);
       setChartError(null);
       setWatchlistError(null);
       setOverviewState("loading");
       setChartState("loading");
-      setOverview(null);
-      setHistory([]);
-      router.push(`/?ticker=${encodeURIComponent(normalized)}`);
+
+      startTransition(() => {
+        router.push(`/?ticker=${encodeURIComponent(normalized)}`);
+      });
     },
     [router]
   );
@@ -76,7 +87,9 @@ function OverviewClient() {
     setWatchlistError(null);
     setOverviewState("idle");
     setChartState("idle");
-    router.push("/");
+    startTransition(() => {
+      router.push("/");
+    });
   }, [router]);
 
   const refreshWatchlist = useCallback(async () => {
@@ -95,10 +108,13 @@ function OverviewClient() {
   useEffect(() => {
     if (query.trim().length < 2) {
       setResults([]);
+      setSearching(false);
       return;
     }
+
     let cancelled = false;
     setSearching(true);
+
     const timer = window.setTimeout(() => {
       api
         .search(query)
@@ -112,6 +128,7 @@ function OverviewClient() {
           if (!cancelled) setSearching(false);
         });
     }, 250);
+
     return () => {
       cancelled = true;
       window.clearTimeout(timer);
@@ -125,18 +142,17 @@ function OverviewClient() {
       setHistory([]);
       setOverviewError(null);
       setChartError(null);
-      setWatchlistError(null);
       setOverviewState("idle");
       setChartState("idle");
       return;
     }
+
     if (lastTickerRef.current === selectedTicker) return;
 
     let cancelled = false;
     lastTickerRef.current = selectedTicker;
     setOverviewState("loading");
     setOverviewError(null);
-    setWatchlistError(null);
     setOverview(null);
 
     api
@@ -149,13 +165,15 @@ function OverviewClient() {
       .catch((exc: Error) => {
         if (cancelled) return;
         setOverview(null);
+        setChartState("idle");
+        setHistory([]);
+
         if (exc instanceof ApiError && exc.status === 404) {
           setOverviewState("invalid");
           setOverviewError("Ticker not found");
-          setChartState("idle");
-          setHistory([]);
           return;
         }
+
         setOverviewState("error");
         setOverviewError(exc.message || "Ticker data unavailable");
       });
@@ -167,6 +185,7 @@ function OverviewClient() {
 
   useEffect(() => {
     if (!selectedTicker) return;
+
     let cancelled = false;
     setChartState("loading");
     setChartError(null);
@@ -193,11 +212,14 @@ function OverviewClient() {
 
   async function onSearchSubmit(event: FormEvent<HTMLFormElement>) {
     event.preventDefault();
-    if (results[0]) selectTicker(results[0].symbol);
-    else if (query.trim()) selectTicker(query);
+    if (results[0]) {
+      navigateToTicker(results[0].symbol);
+      return;
+    }
+    if (query.trim()) navigateToTicker(query);
   }
 
-  async function toggleWatchlist() {
+  async function addOrRemoveCurrentTicker() {
     if (!selectedTicker) return;
     try {
       const next = isSaved ? await api.removeWatchlist(selectedTicker) : await api.addWatchlist(selectedTicker);
@@ -208,323 +230,312 @@ function OverviewClient() {
     }
   }
 
-  const summaryTicker = overview?.profile.symbol || selectedTicker;
-  const summaryName =
-    overviewState === "invalid"
-      ? "Ticker not found"
-      : overview?.profile.name || (selectedTicker ? "Loading ticker..." : "No ticker selected");
-
-  return (
-    <main className="shell">
-      <aside className="sidebar">
-        <div className="brand">
-          <div className="brand-mark">P</div>
-          <div>
-            <div className="brand-name">Prism</div>
-            <div className="brand-sub">Overview</div>
-          </div>
-        </div>
+  async function removeFromWatchlist(symbol: string) {
+    try {
+      const next = await api.removeWatchlist(symbol);
+      setWatchlist(next);
+      setWatchlistError(null);
+    } catch (exc) {
+      setWatchlistError(exc instanceof Error ? exc.message : "Could not update watchlist");
+    }
+  }
 
-        <form className="search-form" onSubmit={onSearchSubmit}>
-          <label htmlFor="ticker-search">Ticker Search</label>
-          <div className="search-box">
-            <Search size={16} aria-hidden />
-            <input id="ticker-search" value={query} onChange={(event) => setQuery(event.target.value)} placeholder="AAPL, Microsoft..." />
-          </div>
-          {query.trim().length >= 2 && (
-            <div className="search-results">
-              {searching && <div className="muted-row">Searching...</div>}
-              {!searching && results.length === 0 && <div className="muted-row">No matches</div>}
-              {results.map((result) => (
-                <button key={`${result.symbol}-${result.exchange}`} type="button" onClick={() => selectTicker(result.symbol)}>
-                  <span>{result.symbol}</span>
-                  <small>{result.name}</small>
-                </button>
-              ))}
+  const shell = (
+    <AppShell
+      sidebar={
+        <Sidebar
+          navItems={OVERVIEW_NAV_ITEMS}
+          selectedKey="overview"
+          currentTicker={selectedTicker}
+          watchlist={watchlist}
+          watchlistError={watchlistError}
+          onSelectTicker={navigateToTicker}
+          onRemoveTicker={removeFromWatchlist}
+        />
+      }
+      topbar={
+        <TopBar
+          query={query}
+          searching={searching}
+          results={results}
+          marketStatus={clockSnapshot}
+          onChangeQuery={setQuery}
+          onSubmit={onSearchSubmit}
+          onSelectTicker={navigateToTicker}
+        />
+      }
+    >
+      <MarketStrip indices={marketCards} />
+      {!selectedTicker ? <EmptyOverviewState watchlist={watchlist} onSelectTicker={navigateToTicker} /> : null}
+      {selectedTicker && overviewState === "loading" ? <LoadingOverviewState symbol={selectedTicker} /> : null}
+      {selectedTicker && overviewState === "invalid" ? <InvalidTickerState symbol={selectedTicker} onClear={clearTicker} /> : null}
+      {selectedTicker && overviewState === "error" ? <ErrorOverviewState message={overviewError || "Ticker data unavailable"} /> : null}
+      {overview && overviewState === "ready" ? (
+        <>
+          <TickerHeader overview={overview} onToggleWatchlist={addOrRemoveCurrentTicker} isSaved={isSaved} />
+          <KPIStrip items={kpis} />
+          <div className="psm-main-grid">
+            <div className="psm-column">
+              <ChartCard symbol={overview.profile.symbol} period={period} points={history} chartState={chartState} chartError={chartError} onChangePeriod={setPeriod} />
+              <SignalCard overview={overview} />
             </div>
-          )}
-        </form>
-
-        <section className="selected-summary">
-          <div className="section-label">Selected</div>
-          <div className="summary-symbol">{summaryTicker || "-"}</div>
-          <div className="summary-name">{summaryName}</div>
-          {overview ? (
-            <>
-              <div className="summary-price">
-                {fmtCurrency(overview.quote.price)}
-                <span className={deltaClass(overview.quote.change_pct)}>{fmtPct(overview.quote.change_pct, 2, true)}</span>
-              </div>
-              <button className="watch-action" type="button" onClick={toggleWatchlist}>
-                {isSaved ? <BookmarkMinus size={16} aria-hidden /> : <BookmarkPlus size={16} aria-hidden />}
-                {isSaved ? "Remove" : "Save"}
-              </button>
-            </>
-          ) : selectedTicker ? (
-            <div className="empty-copy">{overviewState === "loading" ? "Fetching overview..." : "Search for another ticker."}</div>
-          ) : (
-            <div className="empty-copy">No ticker selected</div>
-          )}
-          {watchlistError && <div className="inline-note error-copy">{watchlistError}</div>}
-        </section>
-
-        <section className="watchlist">
-          <div className="section-label">Watchlist</div>
-          {watchlist.items.length === 0 && <div className="empty-copy">No saved tickers</div>}
-          {watchlist.items.map((item) => (
-            <button key={item.symbol} type="button" className="watch-row" onClick={() => selectTicker(item.symbol)}>
-              <span>{item.symbol}</span>
-              <span>{fmtCurrency(item.quote?.price)}</span>
-              <span className={deltaClass(item.quote?.change_pct)}>{fmtPct(item.quote?.change_pct, 2, true)}</span>
-            </button>
-          ))}
-          <div className="watch-count">
-            {watchlist.items.length}/{watchlist.limit}
-          </div>
-        </section>
-      </aside>
-
-      <section className="content">
-        <MarketBar indices={market} />
-        {!selectedTicker && <EmptyState watchlist={watchlist} onSelect={selectTicker} />}
-        {selectedTicker && overviewState === "loading" && <div className="empty-panel">Loading {selectedTicker}...</div>}
-        {selectedTicker && overviewState === "invalid" && <InvalidTickerState symbol={selectedTicker} onClear={clearTicker} />}
-        {selectedTicker && overviewState === "error" && <div className="error-panel">{overviewError || "Ticker data unavailable"}</div>}
-        {overview && overviewState === "ready" && (
-          <article className="overview">
-            <header className="company-header">
-              <div>
-                <div className="eyebrow">{overview.profile.exchange || "Ticker"}</div>
-                <div className="header-title-row">
-                  <h1>{overview.profile.name}</h1>
-                  {overview.meta.is_partial && <span className="status-chip">Partial Data</span>}
-                </div>
-                <p>{identityLine(overview)}</p>
-              </div>
-              <div className="quote-block">
-                <span>{fmtCurrency(overview.quote.price)}</span>
-                <strong className={deltaClass(overview.quote.change_pct)}>{fmtPct(overview.quote.change_pct, 2, true)}</strong>
-                <small>Prev close {fmtCurrency(overview.quote.prev_close)}</small>
-              </div>
-            </header>
-
-            {overview.meta.is_partial && <div className="inline-note">Some fields are unavailable for this ticker. Available data is still shown below.</div>}
-
-            <section className="signal-grid">
-              {overview.signals.map((signal) => (
-                <div className={`signal ${signal.state}`} key={signal.key}>
-                  <span>{signal.key}</span>
-                  <strong>{signal.value}</strong>
-                  <small>{signal.description}</small>
-                </div>
-              ))}
-            </section>
-
-            <section className="stat-grid">
-              <Stat label="Market Cap" value={fmtLarge(overview.stats.market_cap)} missing={overview.stats.market_cap == null} />
-              <Stat label="P/E TTM" value={fmtNumber(overview.stats.trailing_pe)} missing={overview.stats.trailing_pe == null} />
-              <Stat label="EPS TTM" value={fmtCurrency(overview.stats.trailing_eps)} missing={overview.stats.trailing_eps == null} />
-              <Stat label="Volume" value={fmtNumber(overview.stats.volume, 0)} missing={overview.stats.volume == null} />
-              <Stat label="Avg Volume" value={fmtNumber(overview.stats.average_volume, 0)} missing={overview.stats.average_volume == null} />
-              <Stat label="Beta" value={fmtNumber(overview.stats.beta)} missing={overview.stats.beta == null} />
-            </section>
-
-            <section className="split-row">
-              <RangeCard overview={overview} />
-              <ShortInterest overview={overview} />
-            </section>
-
-            <section className="split-row">
+            <div className="psm-column">
+              <DataStatusCard overview={overview} missingFields={missingFields} />
               <ProfileCard overview={overview} />
-              <SourceCard overview={overview} />
-            </section>
-
-            <section className="chart-shell">
-              <div className="chart-head">
-                <div>
-                  <div className="section-label">Price History</div>
-                  <strong>{overview.profile.symbol}</strong>
-                </div>
-                <div className="segmented">
-                  {PERIODS.map((option) => (
-                    <button key={option.key} type="button" className={period === option.key ? "active" : ""} onClick={() => setPeriod(option.key)}>
-                      {option.label}
-                    </button>
-                  ))}
-                </div>
-              </div>
-              {chartState === "loading" && <div className="empty-panel compact-panel">Loading {period.toUpperCase()} history...</div>}
-              {chartState === "error" && <div className="error-panel compact-panel">{chartError || "Could not load chart history"}</div>}
-              {chartState === "ready" && <PriceChart symbol={overview.profile.symbol} points={history} />}
-            </section>
-          </article>
-        )}
-      </section>
-    </main>
+              <ShortInterestCard overview={overview} />
+              <StatsCard overview={overview} />
+            </div>
+          </div>
+        </>
+      ) : null}
+    </AppShell>
   );
+
+  return shell;
 }
 
-function MarketBar({ indices }: { indices: MarketIndex[] }) {
-  if (!indices.length) return <div className="market-bar empty">Market data unavailable</div>;
+function MarketStrip({ indices }: { indices: MarketIndex[] }) {
+  if (!indices.length) {
+    return <section className="psm-card-empty psm-market-card">Market data is temporarily unavailable.</section>;
+  }
+
   return (
-    <section className="market-bar">
+    <section className="psm-market-strip" aria-label="Market indices">
       {indices.map((index) => (
-        <div className="market-item" key={index.name}>
-          <span>{index.name}</span>
-          <strong>{fmtNumber(index.price)}</strong>
-          <small className={deltaClass(index.change_pct)}>{fmtPct(index.change_pct, 2, true)}</small>
-        </div>
+        <article key={index.name} className="psm-market-card">
+          <span className="psm-market-name">{index.name}</span>
+          <span className="psm-market-price">{fmtNumber(index.price)}</span>
+          <span className={`psm-market-change ${deltaClass(index.change_pct)}`}>{fmtPct(index.change_pct, 2, true)}</span>
+        </article>
       ))}
     </section>
   );
 }
 
-function EmptyState({ watchlist, onSelect }: { watchlist: WatchlistResponse; onSelect: (symbol: string) => void }) {
+function EmptyOverviewState({ watchlist, onSelectTicker }: { watchlist: WatchlistResponse; onSelectTicker: (symbol: string) => void }) {
   return (
-    <section className="empty-state">
-      <h1>Overview</h1>
+    <section className="psm-state-panel">
+      <div className="psm-state-title">Overview</div>
+      <h1>Choose a ticker to enter the workbench.</h1>
+      <p>Search from the top bar or jump into one of your saved symbols from the sidebar watchlist.</p>
       {watchlist.items.length ? (
-        <>
-          <p>Select a saved ticker to load its company profile, quote, stats, and chart.</p>
-          <div className="watch-table">
-            {watchlist.items.map((item) => (
-              <button key={item.symbol} type="button" onClick={() => onSelect(item.symbol)}>
-                <span>{item.symbol}</span>
-                <span>{fmtCurrency(item.quote?.price)}</span>
-                <span className={deltaClass(item.quote?.change_pct)}>{fmtPct(item.quote?.change_pct, 2, true)}</span>
-              </button>
-            ))}
-          </div>
-        </>
-      ) : (
-        <p>Search for a ticker to begin.</p>
-      )}
+        <div className="psm-stack">
+          {watchlist.items.map((item) => (
+            <button key={item.symbol} type="button" className="psm-ghost-action" onClick={() => onSelectTicker(item.symbol)}>
+              {item.symbol}
+            </button>
+          ))}
+        </div>
+      ) : null}
     </section>
   );
 }
 
+function LoadingOverviewState({ symbol }: { symbol: string }) {
+  return (
+    <div className="psm-loading-shell">
+      <section className="psm-state-panel">
+        <div className="psm-state-title">Loading</div>
+        <h1>{symbol}</h1>
+        <p>Fetching quote, profile, signals, and supporting metrics.</p>
+      </section>
+      <section className="psm-card psm-skeleton" />
+      <section className="psm-card psm-skeleton" />
+    </div>
+  );
+}
+
 function InvalidTickerState({ symbol, onClear }: { symbol: string; onClear: () => void }) {
   return (
-    <section className="error-panel invalid-state">
+    <section className="psm-state-panel">
+      <span className="psm-status-chip invalid">Invalid Ticker</span>
       <h1>{symbol}</h1>
-      <p>This ticker could not be resolved to usable market data.</p>
-      <button type="button" className="ghost-action" onClick={onClear}>
-        Back to Search
+      <p>This symbol could not be resolved into usable market data. Try another search or return to the empty workspace.</p>
+      <button type="button" className="psm-ghost-action" onClick={onClear}>
+        Clear Selection
       </button>
     </section>
   );
 }
 
-function Stat({ label, value, missing = false }: { label: string; value: string; missing?: boolean }) {
+function ErrorOverviewState({ message }: { message: string }) {
   return (
-    <div className="stat">
-      <span>{label}</span>
-      <strong className={missing ? "missing-value" : undefined}>{missing ? "Unavailable" : value}</strong>
-    </div>
+    <section className="psm-state-panel">
+      <span className="psm-status-chip invalid">Data Error</span>
+      <h1>Overview unavailable</h1>
+      <p>{message}</p>
+    </section>
   );
 }
 
-function RangeCard({ overview }: { overview: TickerOverview }) {
-  const low = overview.range_52w.low;
-  const high = overview.range_52w.high;
-  const price = overview.range_52w.price;
-  const pct = low != null && high != null && price != null && high > low ? Math.max(0, Math.min(100, ((price - low) / (high - low)) * 100)) : null;
+function SignalCard({ overview }: { overview: TickerOverview }) {
   return (
-    <div className="detail-panel">
-      <div className="section-label">52 Week Range</div>
-      {pct === null ? (
-        <div className="empty-copy">Range unavailable</div>
-      ) : (
-        <>
-          <div className="range-values">
-            <span>{fmtCurrency(low)}</span>
-            <strong>{fmtCurrency(price)}</strong>
-            <span>{fmtCurrency(high)}</span>
-          </div>
-          <div className="range-rail">
-            <span style={{ width: `${pct}%` }} />
-            <i style={{ left: `${pct}%` }} />
-          </div>
-        </>
-      )}
-    </div>
+    <section className="psm-card">
+      <div className="psm-card-head">
+        <div>
+          <div className="psm-eyebrow">Signals</div>
+          <h2 className="psm-card-title">Readthrough</h2>
+        </div>
+      </div>
+      <div className="psm-signal-grid">
+        {overview.signals.map((signal) => (
+          <article key={signal.key} className={`psm-signal ${signalTone(signal.state)}`}>
+            <span className="psm-signal-key">{signal.key}</span>
+            <span className="psm-signal-value">{signal.value}</span>
+            <span className="psm-signal-copy">{signal.description}</span>
+          </article>
+        ))}
+      </div>
+    </section>
   );
 }
 
-function ShortInterest({ overview }: { overview: TickerOverview }) {
-  const short = overview.short_interest;
+function DataStatusCard({ overview, missingFields }: { overview: TickerOverview; missingFields: string[] }) {
+  const entries = Object.entries(overview.meta.sources).slice(0, 6);
+
   return (
-    <div className="detail-panel">
-      <div className="section-label">Short Interest</div>
-      <div className="mini-grid">
-        <Stat label="Short Float" value={fmtPct(short.short_percent_of_float)} missing={short.short_percent_of_float == null} />
-        <Stat label="Days Cover" value={fmtNumber(short.short_ratio)} missing={short.short_ratio == null} />
-        <Stat label="Shares Short" value={fmtNumber(short.shares_short, 0)} missing={short.shares_short == null} />
-        <Stat label="Prior Delta" value={fmtPct(short.shares_short_delta_pct, 1, true)} missing={short.shares_short_delta_pct == null} />
+    <section className="psm-card">
+      <div className="psm-card-head">
+        <div>
+          <div className="psm-eyebrow">Data Quality</div>
+          <h2 className="psm-card-title">Coverage</h2>
+        </div>
+        <span className={`psm-status-chip${overview.meta.is_partial ? " partial" : ""}`}>{overview.meta.status}</span>
       </div>
-    </div>
+      <p className="psm-quality-copy">{availableFieldSummary(overview)}</p>
+      {overview.meta.is_partial ? (
+        <div className="psm-stack">
+          {missingFields.length ? missingFields.slice(0, 8).map((field) => <span key={field} className="psm-field-tag missing">{field}</span>) : null}
+        </div>
+      ) : null}
+      <div className="psm-source-list">
+        {entries.length ? (
+          entries.map(([field, source]) => (
+            <div className="psm-source-row" key={field}>
+              <span className="psm-source-key">{field}</span>
+              <span className="psm-source-value">{source}</span>
+            </div>
+          ))
+        ) : (
+          <div className="psm-source-row">
+            <span className="psm-source-key">Sources</span>
+            <span className="psm-source-value">Unavailable</span>
+          </div>
+        )}
+      </div>
+    </section>
   );
 }
 
 function ProfileCard({ overview }: { overview: TickerOverview }) {
   return (
-    <div className="detail-panel">
-      <div className="section-label">Company Profile</div>
-      <div className="profile-list">
+    <section className="psm-card">
+      <div className="psm-card-head">
         <div>
-          <span>Sector</span>
-          <strong className={!overview.profile.sector ? "missing-value" : undefined}>{overview.profile.sector || "Unavailable"}</strong>
+          <div className="psm-eyebrow">Company Profile</div>
+          <h2 className="psm-card-title">Context</h2>
         </div>
-        <div>
-          <span>Industry</span>
-          <strong className={!overview.profile.industry ? "missing-value" : undefined}>{overview.profile.industry || "Unavailable"}</strong>
+      </div>
+      <div className="psm-profile-list">
+        <div className="psm-profile-row">
+          <span className="psm-profile-key">Sector</span>
+          <span className="psm-profile-value">{overview.profile.sector || "Unavailable"}</span>
+        </div>
+        <div className="psm-profile-row">
+          <span className="psm-profile-key">Industry</span>
+          <span className="psm-profile-value">{overview.profile.industry || "Unavailable"}</span>
+        </div>
+        <div className="psm-profile-row">
+          <span className="psm-profile-key">Exchange</span>
+          <span className="psm-profile-value">{overview.profile.exchange || "Unavailable"}</span>
         </div>
+        <div className="psm-profile-row">
+          <span className="psm-profile-key">Website</span>
+          <span className="psm-profile-value">
+            {overview.profile.website ? (
+              <a href={overview.profile.website} target="_blank" rel="noreferrer" className="psm-link">
+                {overview.profile.website}
+              </a>
+            ) : (
+              "Unavailable"
+            )}
+          </span>
+        </div>
+      </div>
+      <p className="psm-profile-summary">{overview.profile.summary || "Business summary unavailable."}</p>
+    </section>
+  );
+}
+
+function ShortInterestCard({ overview }: { overview: TickerOverview }) {
+  const short = overview.short_interest;
+  return (
+    <section className="psm-card">
+      <div className="psm-card-head">
         <div>
-          <span>Website</span>
-          {overview.profile.website ? (
-            <a href={overview.profile.website} target="_blank" rel="noreferrer">
-              {overview.profile.website}
-            </a>
-          ) : (
-            <strong className="missing-value">Unavailable</strong>
-          )}
+          <div className="psm-eyebrow">Short Interest</div>
+          <h2 className="psm-card-title">Pressure</h2>
         </div>
       </div>
-      <p className={overview.profile.summary ? "profile-summary" : "profile-summary missing-value"}>
-        {overview.profile.summary || "Business summary unavailable."}
-      </p>
-    </div>
+      <div className="psm-detail-grid">
+        <DetailItem label="Short Float" value={fmtPct(short.short_percent_of_float)} missing={short.short_percent_of_float == null} />
+        <DetailItem label="Days Cover" value={fmtNumber(short.short_ratio)} missing={short.short_ratio == null} />
+        <DetailItem label="Shares Short" value={fmtNumber(short.shares_short, 0)} missing={short.shares_short == null} />
+        <DetailItem label="Prior Delta" value={fmtPct(short.shares_short_delta_pct, 1, true)} missing={short.shares_short_delta_pct == null} />
+      </div>
+    </section>
   );
 }
 
-function SourceCard({ overview }: { overview: TickerOverview }) {
-  const fields = Object.entries(overview.meta.sources).slice(0, 6);
+function StatsCard({ overview }: { overview: TickerOverview }) {
   return (
-    <div className="detail-panel">
-      <div className="section-label">Data Quality</div>
-      <div className="quality-copy">
-        Status <strong>{overview.meta.status}</strong>
+    <section className="psm-card">
+      <div className="psm-card-head">
+        <div>
+          <div className="psm-eyebrow">Overview Stats</div>
+          <h2 className="psm-card-title">Reference</h2>
+        </div>
       </div>
-      <div className="profile-list compact-list">
-        {fields.length ? (
-          fields.map(([field, source]) => (
-            <div key={field}>
-              <span>{field}</span>
-              <strong>{source}</strong>
-            </div>
-          ))
-        ) : (
-          <div>
-            <span>Sources</span>
-            <strong className="missing-value">Unavailable</strong>
-          </div>
-        )}
+      <div className="psm-stat-list">
+        <StatRow label="Market Cap" value={fmtLarge(overview.stats.market_cap)} missing={overview.stats.market_cap == null} />
+        <StatRow label="P/E TTM" value={fmtNumber(overview.stats.trailing_pe)} missing={overview.stats.trailing_pe == null} />
+        <StatRow label="EPS TTM" value={fmtCurrency(overview.stats.trailing_eps)} missing={overview.stats.trailing_eps == null} />
+        <StatRow label="Volume" value={fmtNumber(overview.stats.volume, 0)} missing={overview.stats.volume == null} />
+        <StatRow label="Avg Volume" value={fmtNumber(overview.stats.average_volume, 0)} missing={overview.stats.average_volume == null} />
+        <StatRow label="Beta" value={fmtNumber(overview.stats.beta)} missing={overview.stats.beta == null} />
       </div>
+    </section>
+  );
+}
+
+function DetailItem({ label, value, missing = false }: { label: string; value: string; missing?: boolean }) {
+  return (
+    <article className="psm-detail-item">
+      <span className="psm-stat-label">{label}</span>
+      <span className={`psm-detail-value${missing ? " missing" : ""}`}>{missing ? "Unavailable" : value}</span>
+    </article>
+  );
+}
+
+function StatRow({ label, value, missing = false }: { label: string; value: string; missing?: boolean }) {
+  return (
+    <div className="psm-stat-row">
+      <span className="psm-stat-label">{label}</span>
+      <span className={`psm-stat-value${missing ? " missing" : ""}`}>{missing ? "Unavailable" : value}</span>
     </div>
   );
 }
 
-function identityLine(overview: TickerOverview) {
-  const parts = [overview.profile.symbol, overview.profile.sector, overview.profile.industry].filter(Boolean);
-  return parts.length ? parts.join(" · ") : overview.profile.symbol;
+function LoadingShell() {
+  return (
+    <AppShell
+      sidebar={<aside className="psm-side"><div className="psm-brand"><Image className="psm-brand-mark" src="/design-system/logo-monogram.svg" alt="" width={34} height={34} /><div className="psm-brand-copy"><div className="psm-brand-name">Prism</div><div className="psm-brand-sub">Loading</div></div></div></aside>}
+      topbar={<header className="psm-top"><div className="psm-search-form"><span className="psm-icon icon-search" aria-hidden /><span className="psm-muted-copy">Loading Prism…</span></div></header>}
+    >
+      <div className="psm-loading-shell">
+        <section className="psm-card psm-skeleton" />
+        <section className="psm-card psm-skeleton" />
+      </div>
+    </AppShell>
+  );
 }
-- 
cgit v1.3-2-g0d8e

