Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .git-blame-ignore-revs
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# Bulk reformats. `git config blame.ignoreRevsFile .git-blame-ignore-revs`
ab0a1a149be19c641511b1f39466f9cb17f1ef32 # prettier, 13 files
6 changes: 3 additions & 3 deletions .github/workflows/auto-merge.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,10 @@ name: Auto-merge

on:
workflow_run:
workflows: ['CI']
workflows: ["CI"]
types: [completed]
schedule:
- cron: '*/10 * * * *'
- cron: "*/10 * * * *"
workflow_dispatch: {}

# Declared on the CALLER as well as inside the reusable workflow: a called
Expand All @@ -46,4 +46,4 @@ jobs:
# SPACE-separated: the sweep word-splits this. A comma would become one
# bogus token, every dispatch would fail, and the only symptom would be
# that nothing deploys — while the sweep still reported success.
rearm_workflows: 'ci.yml'
rearm_workflows: "ci.yml"
31 changes: 31 additions & 0 deletions .prettierignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# Build output and vendored trees — formatting these is noise.
node_modules
.next
dist
build
out
coverage
.turbo
.vercel
*.min.js
*.min.css

# Generated during a build, so it is absent locally and present in CI — which
# makes a clean local --check no evidence at all. Contentlayer's output also
# uses import assertions, which prettier's parser rejects outright.
.contentlayer
.astro
.svelte-kit
storybook-static
test-results
playwright-report

# Lockfiles are generated; prettier would rewrite them wholesale.
package-lock.json
pnpm-lock.yaml
yarn.lock

# Markdown is deliberately out of scope for now. Prettier rewraps prose, which
# is where it is most opinionated and least useful, and it would bury the real
# diff. Remove this line when you want docs formatted too.
*.md
9 changes: 9 additions & 0 deletions .prettierrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"semi": true,
"singleQuote": false,
"printWidth": 100,
"tabWidth": 2,
"trailingComma": "all",
"arrowParens": "always",
"endOfLine": "lf"
}
4 changes: 1 addition & 3 deletions next.config.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
import type { NextConfig } from "next";

const nextConfig: NextConfig = {
/* config options here */
};
const nextConfig: NextConfig = {/* config options here */};

export default nextConfig;
20 changes: 20 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 5 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,10 @@
"typecheck": "tsc --noEmit",
"test": "vitest run",
"test:watch": "vitest",
"verify": "npm run lint && npm run typecheck && npm run test",
"analyze": "tsx scripts/analyze-cli.ts"
"verify": "npm run format:check && npm run lint && npm run typecheck && npm run test",
"analyze": "tsx scripts/analyze-cli.ts",
"format": "prettier --write .",
"format:check": "prettier --check ."
},
"dependencies": {
"ai-kit": "github:bitbaum/ai-kit#v0.4.0",
Expand All @@ -31,6 +33,7 @@
"@types/react-dom": "^19",
"eslint": "^9",
"eslint-config-next": "16.3.3",
"prettier": "3.9.6",
"tailwindcss": "^4",
"tsx": "^4.23.12",
"typescript": "^5",
Expand Down
9 changes: 7 additions & 2 deletions scripts/analyze-cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,10 @@ function loadDotEnvLocal(): void {
const key = trimmed.slice(0, eq).trim();
let value = trimmed.slice(eq + 1).trim();
// Strip simple surrounding quotes.
if ((value.startsWith('"') && value.endsWith('"')) || (value.startsWith("'") && value.endsWith("'"))) {
if (
(value.startsWith('"') && value.endsWith('"')) ||
(value.startsWith("'") && value.endsWith("'"))
) {
value = value.slice(1, -1);
}
if (!process.env[key]) process.env[key] = value;
Expand Down Expand Up @@ -93,7 +96,9 @@ function renderMarkdown(url: string, result: AnalysisResult): string {
lines.push("---");
lines.push("");
const truncNote = result.truncated ? " (truncated — article was longer)" : "";
lines.push(`*Generated by truthseeker · ${result.fetched.textLength.toLocaleString()} chars examined${truncNote} · ${result.durationMs}ms*`);
lines.push(
`*Generated by truthseeker · ${result.fetched.textLength.toLocaleString()} chars examined${truncNote} · ${result.durationMs}ms*`,
);
return lines.join("\n");
}

Expand Down
100 changes: 52 additions & 48 deletions src/app/globals.css
Original file line number Diff line number Diff line change
Expand Up @@ -10,106 +10,110 @@

:root {
/* -- Tier 1: primitive palette (raw values — not used directly by components) */
--primitive-ink-950: #0c0e12; /* near-black, cool slate */
--primitive-ink-950: #0c0e12; /* near-black, cool slate */
--primitive-ink-900: #14171d;
--primitive-ink-800: #1c212a;
--primitive-ink-700: #2a313d;
--primitive-ink-500: #5b6675;
--primitive-ink-400: #7d899a;
--primitive-paper-50: #f7f6f3; /* warm forensic paper, not pure white */
--primitive-paper-0: #fcfbf9;
--primitive-line: #e4e1da;
--primitive-paper-50: #f7f6f3; /* warm forensic paper, not pure white */
--primitive-paper-0: #fcfbf9;
--primitive-line: #e4e1da;

--primitive-amber-500: #c8851a; /* highlighter / evidence-mark — brand */
--primitive-amber-500: #c8851a; /* highlighter / evidence-mark — brand */
--primitive-amber-400: #e0a234;

/* claim-type primitives — the product's signature spectrum */
--primitive-teal-500: #0e8c87; /* factual — verifiable, measured */
--primitive-teal-400: #34b3ae;
--primitive-violet-500: #7a5cd6; /* normative — values / "should" */
--primitive-teal-500: #0e8c87; /* factual — verifiable, measured */
--primitive-teal-400: #34b3ae;
--primitive-violet-500: #7a5cd6; /* normative — values / "should" */
--primitive-violet-400: #9a82e6;
--primitive-rust-500: #c25a2e; /* causal — chains of reasoning */
--primitive-rust-400: #df7a4e;
--primitive-rust-500: #c25a2e; /* causal — chains of reasoning */
--primitive-rust-400: #df7a4e;

/* -- Tier 2: semantic tokens (what things MEAN — components use only these) */
--color-bg: var(--primitive-paper-50);
--color-surface: var(--primitive-paper-0);
--color-bg: var(--primitive-paper-50);
--color-surface: var(--primitive-paper-0);
--color-surface-raised: #ffffff;
--color-border: var(--primitive-line);
--color-border: var(--primitive-line);
--color-border-strong: #cfcabf;
--color-text: var(--primitive-ink-950);
--color-text-muted: var(--primitive-ink-500);
--color-brand: var(--primitive-amber-500);
--color-on-brand: #ffffff;
--color-text: var(--primitive-ink-950);
--color-text-muted: var(--primitive-ink-500);
--color-brand: var(--primitive-amber-500);
--color-on-brand: #ffffff;

/* mono accent — the "evidence log" technical voice */
--color-mono-accent: var(--primitive-ink-700);

/* claim-type semantic tokens */
--color-factual: var(--primitive-teal-500);
--color-factual: var(--primitive-teal-500);
--color-factual-soft: color-mix(in srgb, var(--primitive-teal-500) 12%, var(--color-surface));
--color-normative: var(--primitive-violet-500);
--color-normative: var(--primitive-violet-500);
--color-normative-soft: color-mix(in srgb, var(--primitive-violet-500) 12%, var(--color-surface));
--color-causal: var(--primitive-rust-500);
--color-causal: var(--primitive-rust-500);
--color-causal-soft: color-mix(in srgb, var(--primitive-rust-500) 12%, var(--color-surface));

/* error / warning — a forensic flag, not generic amber */
--color-flag: #b4451f;
--color-flag-soft: color-mix(in srgb, #b4451f 8%, var(--color-surface));
--color-flag: #b4451f;
--color-flag-soft: color-mix(in srgb, #b4451f 8%, var(--color-surface));

/* radii / shadows */
--radius-card: 4px; /* sharp corners suit the forensic concept */
--radius-card: 4px; /* sharp corners suit the forensic concept */
--radius-chip: 3px;
--shadow-card: 0 1px 2px rgb(12 14 18 / 0.04), 0 1px 8px rgb(12 14 18 / 0.04);
}

@media (prefers-color-scheme: dark) {
:root {
--color-bg: var(--primitive-ink-950);
--color-surface: var(--primitive-ink-900);
--color-bg: var(--primitive-ink-950);
--color-surface: var(--primitive-ink-900);
--color-surface-raised: var(--primitive-ink-800);
--color-border: var(--primitive-ink-800);
--color-border: var(--primitive-ink-800);
--color-border-strong: var(--primitive-ink-700);
--color-text: var(--primitive-paper-50);
--color-text-muted: var(--primitive-ink-400);
--color-brand: var(--primitive-amber-400);
--color-on-brand: var(--primitive-ink-950);
--color-text: var(--primitive-paper-50);
--color-text-muted: var(--primitive-ink-400);
--color-brand: var(--primitive-amber-400);
--color-on-brand: var(--primitive-ink-950);
--color-mono-accent: var(--primitive-ink-400);

--color-factual: var(--primitive-teal-400);
--color-factual: var(--primitive-teal-400);
--color-factual-soft: color-mix(in srgb, var(--primitive-teal-400) 14%, var(--color-surface));
--color-normative: var(--primitive-violet-400);
--color-normative-soft: color-mix(in srgb, var(--primitive-violet-400) 14%, var(--color-surface));
--color-causal: var(--primitive-rust-400);
--color-normative: var(--primitive-violet-400);
--color-normative-soft: color-mix(
in srgb,
var(--primitive-violet-400) 14%,
var(--color-surface)
);
--color-causal: var(--primitive-rust-400);
--color-causal-soft: color-mix(in srgb, var(--primitive-rust-400) 14%, var(--color-surface));

--color-flag: #e07a52;
--color-flag-soft: color-mix(in srgb, #e07a52 12%, var(--color-surface));
--color-flag: #e07a52;
--color-flag-soft: color-mix(in srgb, #e07a52 12%, var(--color-surface));

--shadow-card: 0 1px 2px rgb(0 0 0 / 0.3), 0 1px 10px rgb(0 0 0 / 0.25);
}
}

@theme inline {
--color-bg: var(--color-bg);
--color-surface: var(--color-surface);
--color-bg: var(--color-bg);
--color-surface: var(--color-surface);
--color-surface-raised: var(--color-surface-raised);
--color-border: var(--color-border);
--color-border: var(--color-border);
--color-border-strong: var(--color-border-strong);
--color-text: var(--color-text);
--color-text-muted: var(--color-text-muted);
--color-brand: var(--color-brand);
--color-on-brand: var(--color-on-brand);
--color-mono-accent: var(--color-mono-accent);
--color-text: var(--color-text);
--color-text-muted: var(--color-text-muted);
--color-brand: var(--color-brand);
--color-on-brand: var(--color-on-brand);
--color-mono-accent: var(--color-mono-accent);

--color-factual: var(--color-factual);
--color-factual: var(--color-factual);
--color-factual-soft: var(--color-factual-soft);
--color-normative: var(--color-normative);
--color-normative: var(--color-normative);
--color-normative-soft: var(--color-normative-soft);
--color-causal: var(--color-causal);
--color-causal: var(--color-causal);
--color-causal-soft: var(--color-causal-soft);

--color-flag: var(--color-flag);
--color-flag: var(--color-flag);
--color-flag-soft: var(--color-flag-soft);

--radius-card: var(--radius-card);
Expand Down
5 changes: 1 addition & 4 deletions src/app/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,10 +32,7 @@ export default function RootLayout({
children: React.ReactNode;
}>) {
return (
<html
lang="en"
className={`${grotesk.variable} ${monoAccent.variable} h-full antialiased`}
>
<html lang="en" className={`${grotesk.variable} ${monoAccent.variable} h-full antialiased`}>
<body className="min-h-full flex flex-col">{children}</body>
</html>
);
Expand Down
23 changes: 10 additions & 13 deletions src/app/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -64,9 +64,8 @@ export default function Home() {
truth<span className="text-brand">/</span>seeker
</h1>
<p className="mt-3 max-w-xl text-text-muted">
Paste an article URL. Get a forensic breakdown — every claim tagged,
its sources named, its biases surfaced, and a first-principles critique
you can argue with.
Paste an article URL. Get a forensic breakdown — every claim tagged, its sources named,
its biases surfaced, and a first-principles critique you can argue with.
</p>
</header>

Expand Down Expand Up @@ -116,7 +115,9 @@ function ClaimLegend() {
<div key={t} className="bg-surface px-4 py-3">
<dt className="flex items-center gap-2">
<span className={`h-2 w-2 rounded-full ${c.dot}`} />
<span className={`font-mono text-xs font-semibold uppercase tracking-wider ${c.text}`}>
<span
className={`font-mono text-xs font-semibold uppercase tracking-wider ${c.text}`}
>
{c.label}
</span>
</dt>
Expand All @@ -136,9 +137,9 @@ function EmptyState() {
How it reads an article
</p>
<p className="max-w-xl text-sm text-text-muted">
truthseeker doesn&apos;t tell you whether an article is true. It exposes
its <span className="text-text">structure</span> so you can judge it
yourself. Every load-bearing claim is sorted into one of three kinds:
truthseeker doesn&apos;t tell you whether an article is true. It exposes its{" "}
<span className="text-text">structure</span> so you can judge it yourself. Every
load-bearing claim is sorted into one of three kinds:
</p>
<ClaimLegend />
<ol className="space-y-2 font-mono text-xs text-text-muted">
Expand Down Expand Up @@ -166,9 +167,7 @@ function LoadingState() {
<span className="absolute inline-flex h-full w-full animate-ping rounded-full bg-brand opacity-60" />
<span className="relative inline-flex h-2.5 w-2.5 rounded-full bg-brand" />
</span>
<p className="font-mono text-xs uppercase tracking-[0.15em] text-text">
Examining
</p>
<p className="font-mono text-xs uppercase tracking-[0.15em] text-text">Examining</p>
</div>
<ul className="space-y-1.5 font-mono text-xs text-text-muted">
<li>→ fetching and cleaning article body</li>
Expand Down Expand Up @@ -262,9 +261,7 @@ function Result({ result }: { result: AnalysisResult }) {
>
<p className="text-text">{c.claim}</p>
<div className="mt-2 flex flex-wrap items-center gap-2 font-mono text-[10px] uppercase tracking-wider">
<span className={`rounded-chip px-1.5 py-0.5 ${style.chip}`}>
{style.label}
</span>
<span className={`rounded-chip px-1.5 py-0.5 ${style.chip}`}>{style.label}</span>
<span className="text-text-muted">{c.evidence_quality}</span>
<span className="text-text-muted">
{c.supported_in_article ? "· supported in article" : "· unsupported"}
Expand Down
Loading