disclosure-bureau/web/app/page.tsx
Luiz Gustavo 67185ff518
Some checks failed
CI / Web — typecheck + lint + build (push) Failing after 40s
CI / Scripts — Python smoke (push) Failing after 3s
CI / Web — npm audit (push) Failing after 31s
CI / Retrieval — golden set (Recall@5 + MRR) (push) Failing after 4s
W3.9: surface the Investigation Bureau on the homepage + /bureau hub
Closes a UX gap the user surfaced: W3.5-3.8 built 8 detectives, 4 new
URL endpoints (/jobs/[id], /h/[id], /c/[slug], /api/h/[id]/red-team)
and a chat tool, but the homepage was unchanged — the bureau was
invisible unless you knew the URL or asked the chat to invoke
request_investigation.

Homepage (web/app/page.tsx):
  - Title `▍ war.gov/ufo — Investigative Wiki` → `▍ The Disclosure Bureau`
  - Subtitle expanded from "Holmes · Poirot · Dupin · Locard" to all 8
    detectives (Holmes · Locard · Dupin · Schneier · Poirot · Taleb ·
    Tetlock · Case-Writer)
  - New `🔎 bureau` topbar link (gold, between graph/stats and batch)
  - BureauSnapshot inserted right after the header

BureauSnapshot (web/components/bureau-snapshot.tsx) — server component:
  - 8 detective tiles with role labels (each in its tone color)
  - 6 clickable counters (evidence / hypotheses / contradictions /
    witnesses / outliers / case reports) — anchor to /bureau#section
  - 6 "recent artefacts" columns surfacing the last 3-4 of each kind:
    hypotheses with prior→posterior + band + ↳reviewed_by marker,
    contradictions with topic + resolution_status, evidence with
    Grade badge + verbatim quote, outliers with title + scope.kind,
    witness analyses with canonical_name + credibility + verdict,
    case reports with slug + link to /c/<slug>
  - "Recent jobs" strip linking to /jobs/[id] color-coded by status
  - Reports read from /data/ufo/case/reports/ via fs.readdir + stat,
    sorted by mtime — no DB round-trip needed for that section

/bureau (web/app/bureau/page.tsx) — full hub:
  - Header with full counts
  - 7 sections (anchored to homepage counter links): Case reports,
    Hypotheses, Evidence, Contradictions, Outliers, Witnesses,
    Recent jobs table — each rendering up to 100 rows
  - Reports section parses frontmatter from each .md to surface topic
    + n_hypotheses + n_evidence on the card

Runtime fixes batched in:
  - Poirot: coerce entity_pk via Number() — node-postgres returns
    BIGINT as string by default; writer's Number.isFinite() rejected
    it as "person_entity_pk required" (j-edgar-hoover retry path)
  - Tetlock: write_calibration rationale cap 600 → 1200 chars. Prompt
    still asks ≤ 600 but a 2× slack beats failing the job on honest
    analysis. Observed live: Tetlock emitted ~620 chars on H-0003 and
    the writer rejected the entire calibration.
  - Case-Writer: Promise.all of 5 queries × max_parallel=2 jobs
    demanded up to 10 connections against the investigator role's
    rolconnlimit=4 → "too many connections for role investigator".
    Sequentialized — the LLM call is the hot path, not these queries.

Smoke results visible now on the homepage:
  - 3 hypotheses (H-0001/2/3) about green fireballs origin
  - 3 contradictions (R-0001/2/3) about color, geographic confinement,
    exclusive-green vs multicolored
  - 2 evidence cards (E-0002/3) Grade B
  - 3 outliers (G-0001/2/3) — including Taleb's deliberate
    meteor-shower-camouflage flag
  - 1 case report at /c/green-fireballs-sandia (Watson 13.4 KB,
    five-act narrative, fully cited)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-23 22:41:28 -03:00

102 lines
4 KiB
TypeScript

import Link from "next/link";
import { listDocuments, readDocument } from "@/lib/wiki";
import { ChatBubble } from "@/components/chat-bubble";
import { AuthBar } from "@/components/auth-bar";
import { BatchProgressBanner } from "@/components/batch-progress-banner";
import { getLocale } from "@/components/locale-toggle";
import { summarize, pickPitch } from "@/lib/doc-summary";
import { DocListFilters } from "@/components/doc-list-filters";
import { BureauSnapshot } from "@/components/bureau-snapshot";
// Read wiki/ filesystem at request time, not build time.
export const dynamic = "force-dynamic";
export default async function Home() {
const ids = await listDocuments();
const locale = await getLocale();
const summaryLang: "pt" | "en" = locale === "en" ? "en" : "pt";
const docs = await Promise.all(
ids.map(async (id) => {
const f = await readDocument(id);
return {
id,
title: (f?.fm.canonical_title as string) ?? id,
pages: (f?.fm.page_count as number) ?? 0,
collection: (f?.fm.collection as string) ?? "uncategorized",
classification: (f?.fm.highest_classification as string) ?? "—",
summary: pickPitch(f?.fm as Record<string, unknown> | undefined, summaryLang) ?? (f?.body ? summarize(f.body, summaryLang) : ""),
};
}),
);
return (
<main className="min-h-screen p-6 md:p-10 max-w-[1600px] mx-auto">
<header className="mb-12 border-b border-[rgba(0,255,156,0.32)] pb-6">
<div className="flex items-start justify-between gap-4 mb-2">
<div className="font-mono text-[10px] text-[#5a6678] tracking-[0.18em] uppercase">
// THE DISCLOSURE BUREAU // CLASSIFIED ARCHIVE //
</div>
<div className="flex items-center gap-2">
<Link
href="/search"
className="font-mono text-xs px-3 py-1.5 border border-[#ffa500] text-[#ffa500] hover:bg-[rgba(255,165,0,0.10)] rounded"
title="Hybrid search (or ⌘K anywhere)"
>
🔍 search
</Link>
<Link
href="/timeline"
className="font-mono text-xs px-3 py-1.5 border border-[#ff8a4d] text-[#ff8a4d] hover:bg-[rgba(255,138,77,0.10)] rounded"
>
📅 timeline
</Link>
<Link
href="/graph"
className="font-mono text-xs px-3 py-1.5 border border-[#7fdbff] text-[#7fdbff] hover:bg-[rgba(127,219,255,0.10)] rounded"
>
🕸 graph
</Link>
<Link
href="/admin/stats"
className="font-mono text-xs px-3 py-1.5 border border-[#a78bfa] text-[#a78bfa] hover:bg-[rgba(167,139,250,0.10)] rounded"
title="Corpus analytics"
>
📊 stats
</Link>
<Link
href="/bureau"
className="font-mono text-xs px-3 py-1.5 border border-[#e0c080] text-[#e0c080] hover:bg-[rgba(224,192,128,0.10)] rounded"
title="Investigation Bureau — detectives, hypotheses, evidence, contradictions, outliers, case reports"
>
🔎 bureau
</Link>
<Link
href="/admin/batch"
className="font-mono text-xs px-3 py-1.5 border border-[rgba(0,255,156,0.30)] text-[#00ff9c] hover:bg-[rgba(0,255,156,0.10)] rounded"
title="Batch rebuild progress"
>
📈 batch
</Link>
<AuthBar />
</div>
</div>
<h1 className="font-mono text-3xl text-[#00ff9c] mb-2">
The Disclosure Bureau
</h1>
<p className="text-[#8896aa] text-sm">
{docs.length} declassified documents · {docs.reduce((s, d) => s + d.pages, 0)} pages ·
investigated by 8 AI detectives (Holmes · Locard · Dupin · Schneier · Poirot · Taleb · Tetlock · Case-Writer)
</p>
</header>
<BureauSnapshot />
<BatchProgressBanner />
<DocListFilters docs={docs} />
<ChatBubble context={{}} />
</main>
);
}