trends sidebar cleanup: minimal display + beige/navy palette

Per Ryan 2026-05-15:
- Removed Vector pass footer (no audience value)
- Removed PHASE 6b comment
- Removed corner brackets + monitoring subtitle + rank prefix
- Top 5 entities now show as Sony +12 / Riedel +8 — entity name
  serif + count mono, right-aligned
- Background #F5EFEB (beige; #F5EFFEB had 7 hex chars, interpreted as
  #F5EFEB) via new --color-trends-bg token
- Text #2F4F5B (navy) via new --color-trends-text token
- Tokens added to redesign-tokens.css palette section

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Claude (Phase B)
2026-05-15 13:53:51 +00:00
parent e791f19bd8
commit 09a24be3de
2 changed files with 28 additions and 43 deletions

View File

@@ -10,17 +10,6 @@ interface Article {
published_at: string | null;
}
function ago(iso: string | null): string {
if (!iso) return "just now";
const t = Date.now() - new Date(iso).getTime();
const m = Math.floor(t / 60000);
if (m < 60) return `${m}m ago`;
return `${Math.floor(m / 60)}h ago`;
}
// Tiny inline entity extractor: count tag occurrences in recent vs older
// articles and surface the top 5 by lift. Not vector — just keyword
// salience. Phase 6b will replace with pgvector semantic similarity.
function detectTrends(recent: Article[], baseline: Article[]) {
const recentCounts: Record<string, number> = {};
for (const a of recent) for (const t of a.tags || []) {
@@ -39,8 +28,11 @@ function detectTrends(recent: Article[], baseline: Article[]) {
return { entity: k, lift, count: c };
});
scored.sort((a, b) => b.lift - a.lift);
const top = scored.slice(0, 5);
return top;
return scored.slice(0, 5);
}
function titleCase(s: string): string {
return s.replace(/\b\w/g, (c) => c.toUpperCase());
}
export default async function TrendsSidebar() {
@@ -55,7 +47,7 @@ export default async function TrendsSidebar() {
const since7d = new Date(Date.now() - 7 * 86400000).toISOString();
const since30d = new Date(Date.now() - 30 * 86400000).toISOString();
const [recent, baseline, total] = await Promise.all([
const [recent, baseline] = await Promise.all([
sb
.from("wp_imported_posts")
.select("title, excerpt, tags, wp_published_at")
@@ -71,7 +63,6 @@ export default async function TrendsSidebar() {
.lt("wp_published_at", since7d)
.order("wp_published_at", { ascending: false })
.limit(800),
sb.from("wp_imported_posts").select("wp_id", { count: "exact", head: true }).eq("status", "published"),
]);
const recentArts: Article[] = (recent.data || []).map((r: any) => ({
@@ -82,47 +73,37 @@ export default async function TrendsSidebar() {
}));
const trends = detectTrends(recentArts, baselineArts);
const nowIso = new Date().toISOString();
return (
<aside
className="rounded border border-[#252525] bg-[var(--color-background-secondary,#111)] p-4 relative"
style={{ borderRadius: "var(--border-radius-md,6px)" }}
className="rounded p-4"
style={{
background: "var(--color-trends-bg, #F5EFEB)",
color: "var(--color-trends-text, #2F4F5B)",
borderRadius: "var(--border-radius-md, 6px)",
}}
aria-label="BB AI detected trends"
>
<span aria-hidden className="absolute top-2 left-2 w-3 h-3 border-t border-l border-[var(--color-text-info,#60a5fa)]" />
<span aria-hidden className="absolute bottom-2 right-2 w-3 h-3 border-b border-r border-[var(--color-text-info,#60a5fa)]" />
<div className="flex items-center justify-between">
<div className="flex items-center justify-between mb-3">
<h2 className="font-serif text-base font-semibold">BB AI · detected trends</h2>
<span className="inline-flex items-center gap-1.5 text-[10px] font-mono uppercase tracking-wider text-[var(--color-text-info,#60a5fa)]">
<span className="bb-pulse-dot" />
<span className="inline-flex items-center gap-1.5 text-[10px] font-mono uppercase tracking-wider opacity-70">
<span className="bb-pulse-dot" style={{ background: "var(--color-trends-text, #2F4F5B)" }} />
live
</span>
</div>
<p className="text-[10px] font-mono text-[#6b7280] mt-1">
monitoring 47 sources · 24h
</p>
<ol className="mt-4 space-y-2">
<ul className="space-y-1.5">
{trends.length === 0 && (
<li className="text-xs text-[#6b7280]">No trend data yet.</li>
<li className="text-xs opacity-60">No trend data yet.</li>
)}
{trends.map((t, i) => (
<li key={t.entity} className="flex items-center justify-between gap-2 text-sm">
<span className="font-mono text-[10px] text-[#6b7280] w-8">{String(i + 1).padStart(2, "0")}</span>
<span className="flex-1 truncate text-[#e5e7eb]">{t.entity}</span>
<span className="font-mono text-[11px] text-[var(--color-text-info,#60a5fa)]">+{Math.round((t.lift - 1) * 100) || t.count}</span>
{trends.map((t) => (
<li key={t.entity} className="flex items-center justify-between text-sm">
<span className="truncate font-serif">{titleCase(t.entity)}</span>
<span className="font-mono text-[12px] opacity-80">
+{Math.round((t.lift - 1) * 100) || t.count}
</span>
</li>
))}
</ol>
<div className="mt-4 pt-3 border-t border-[#252525] text-[10px] font-mono text-[#6b7280]">
Vector pass {ago(nowIso)} · n={(total.count || 0).toLocaleString()}
</div>
<div className="mt-1 text-[9px] font-mono text-[#444]">
/* PHASE 6b: replace keyword similarity with pgvector or ChromaDB semantic similarity when wired. */
</div>
</ul>
</aside>
);
}

View File

@@ -24,6 +24,10 @@
--color-text-warning: #f59e0b;
--color-text-danger: #ef4444;
/* Trends sidebar palette (Ryan, 2026-05-15) */
--color-trends-bg: #F5EFEB;
--color-trends-text: #2F4F5B;
/* Type families */
--font-serif: Georgia, "Times New Roman", "Tinos", serif;
--font-mono: "JetBrains Mono", "Fira Code", "Courier New", Courier, monospace;