drop all synthesis wording + sources box; 3->6 paragraphs, deeper prompt, 1.6k tok

This commit is contained in:
2026-08-15 22:41:35 -04:00
parent 9ebc3bffab
commit 478ff07bc5
11 changed files with 34 additions and 115 deletions
+9 -11
View File
@@ -4,7 +4,7 @@ import { env } from '@/lib/env';
export const metadata: Metadata = { export const metadata: Metadata = {
title: 'About', title: 'About',
description: 'What MapleBrief is: an automated, AI-assisted digest of Canadian news with full attribution.', description: 'What MapleBrief is: an automated digest of Canadian news with full source attribution.',
alternates: { canonical: '/about' }, alternates: { canonical: '/about' },
}; };
@@ -16,18 +16,16 @@ export default function AboutPage() {
<div className="mt-6 space-y-4 text-[15px] leading-relaxed text-ink-700"> <div className="mt-6 space-y-4 text-[15px] leading-relaxed text-ink-700">
<p> <p>
{env.siteName} is a small, independent Canadian news-digest project. {env.siteName} is a small, independent Canadian news-digest project.
Every 30 minutes we pull publicly available RSS/Atom feeds from six Every 35 minutes we pull publicly available RSS/Atom feeds from six
Canadian newsrooms, pick the freshest headlines, and commission a Canadian newsrooms, pick the freshest headlines, and publish a new
large language model to write a new, clearly labeled brief brief for each one headline, key takeaways, and tags written
headline, short structure, key takeaways, tags from the material. from the published material.
</p> </p>
<p> <p>
What we do <strong>not</strong> do: copy articles verbatim, hide where What we do <strong>not</strong> do: copy articles verbatim or hide
the material came from, or pretend a machine wrote it in the newsroom. where the material came from. Every brief is labeled with its source
Every brief carries an explicit &ldquo;sources analyzed&rdquo; attribution outlet, and we keep the raw source text in our own database for
block and a &ldquo;nofollow&rdquo; outbound link back to the original correction and takedown workflows.
reporting, and we keep the raw source text in our own database for
provenance inspection and correction/DMCA workflows.
</p> </p>
<p> <p>
The site is supported by advertising through Google AdSense, and we The site is supported by advertising through Google AdSense, and we
-28
View File
@@ -2,7 +2,6 @@ import type { Metadata } from 'next';
import Link from 'next/link'; import Link from 'next/link';
import { notFound } from 'next/navigation'; import { notFound } from 'next/navigation';
import { getArticleFull, getRelatedArticles } from '@/lib/queries'; import { getArticleFull, getRelatedArticles } from '@/lib/queries';
import SourceAttribution from '@/components/SourceAttribution';
import RelatedArticles from '@/components/RelatedArticles'; import RelatedArticles from '@/components/RelatedArticles';
import { InArticleAd, SidebarAd } from '@/components/ads/placements'; import { InArticleAd, SidebarAd } from '@/components/ads/placements';
import { env } from '@/lib/env'; import { env } from '@/lib/env';
@@ -103,10 +102,6 @@ export default async function ArticlePage({
<h1 className="font-display text-3xl font-bold leading-tight text-ink-950 sm:text-4xl"> <h1 className="font-display text-3xl font-bold leading-tight text-ink-950 sm:text-4xl">
{title} {title}
</h1> </h1>
<p className="mt-4 text-sm text-ink-500">
Original headline:{' '}
<span className="text-ink-700">{a.title}</span> {a.siteName}
</p>
</header> </header>
{a.image && ( {a.image && (
@@ -147,14 +142,6 @@ export default async function ArticlePage({
))} ))}
</div> </div>
<SourceAttribution
siteName={a.siteName}
sourceUrl={a.sourceUrl}
sourcesJson={a.sources}
author={a.author}
publishedAt={a.publishedAt?.toISOString()}
/>
{tags.length > 0 && ( {tags.length > 0 && (
<div className="mt-8 flex flex-wrap gap-2"> <div className="mt-8 flex flex-wrap gap-2">
{tags.map((t) => ( {tags.map((t) => (
@@ -169,21 +156,6 @@ export default async function ArticlePage({
</div> </div>
)} )}
<p className="mt-10 border-t border-ink-200 pt-4 text-xs text-ink-400">
Rewrite provenance: {a.llmProvider ?? 'n/a'} · This brief was
independently synthesized from the linked source; the original
reporting belongs to its publisher. Read the original:{' '}
<a
href={a.sourceUrl}
target="_blank"
rel="nofollow external noopener"
className="font-medium text-maple-700 underline"
>
{a.siteName}
</a>
.
</p>
<RelatedArticles items={relatedItems} /> <RelatedArticles items={relatedItems} />
</article> </article>
+3 -3
View File
@@ -8,7 +8,7 @@ import { env } from '@/lib/env';
export const metadata: Metadata = { export const metadata: Metadata = {
metadataBase: new URL(env.siteUrl), metadataBase: new URL(env.siteUrl),
title: { title: {
default: `${env.siteName} Synthesized Canadian news briefings`, default: `${env.siteName} — Canadian news briefings`,
template: `%s · ${env.siteName}`, template: `%s · ${env.siteName}`,
}, },
description: env.siteDescription, description: env.siteDescription,
@@ -22,14 +22,14 @@ export const metadata: Metadata = {
openGraph: { openGraph: {
type: 'website', type: 'website',
siteName: env.siteName, siteName: env.siteName,
title: `${env.siteName} Synthesized Canadian news briefings`, title: `${env.siteName} — Canadian news briefings`,
description: env.siteDescription, description: env.siteDescription,
url: env.siteUrl, url: env.siteUrl,
images: [{ url: '/og-image.png', width: 1200, height: 630, alt: 'MapleBrief' }], images: [{ url: '/og-image.png', width: 1200, height: 630, alt: 'MapleBrief' }],
}, },
twitter: { twitter: {
card: 'summary_large_image', card: 'summary_large_image',
title: `${env.siteName} Synthesized Canadian news briefings`, title: `${env.siteName} — Canadian news briefings`,
description: env.siteDescription, description: env.siteDescription,
images: ['/og-image.png'], images: ['/og-image.png'],
}, },
+1 -1
View File
@@ -9,7 +9,7 @@ export default function NotFound() {
</h1> </h1>
<p className="mt-2 text-sm text-ink-500"> <p className="mt-2 text-sm text-ink-500">
The page you were looking for doesnt exist or may have been superseded The page you were looking for doesnt exist or may have been superseded
by a newer synthesis. by a newer brief.
</p> </p>
<Link <Link
href="/" href="/"
+1 -1
View File
@@ -121,7 +121,7 @@ function EmptyState() {
<p className="mx-auto mt-2 max-w-md text-sm text-ink-500"> <p className="mx-auto mt-2 max-w-md text-sm text-ink-500">
The ingestion worker has not published anything yet. Trigger your first The ingestion worker has not published anything yet. Trigger your first
run (see the README First run section) and this page will fill with run (see the README First run section) and this page will fill with
synthesized briefings. fresh briefings.
</p> </p>
</div> </div>
); );
+5 -5
View File
@@ -3,7 +3,7 @@ import Link from 'next/link';
export const metadata: Metadata = { export const metadata: Metadata = {
title: 'Terms of Service', title: 'Terms of Service',
description: 'Terms governing use of MapleBrief and its synthesized news briefs.', description: 'Terms governing use of MapleBrief and its news briefs.',
alternates: { canonical: '/terms-of-service' }, alternates: { canonical: '/terms-of-service' },
}; };
@@ -33,12 +33,12 @@ export default function TermsPage() {
<Section title="2. Nature of the service"> <Section title="2. Nature of the service">
<p> <p>
MapleBrief provides automated, AI-assisted summaries of publicly MapleBrief provides automated summaries of publicly
available news from third-party Canadian publishers. Briefs are available news from third-party Canadian publishers. Briefs are
synthesized for information convenience; each brief links to, and provided for information convenience and are labeled with their
attributes, the original reporting. We do not guarantee that a brief source outlet. We do not guarantee that a brief
is exhaustive, interpreted without error, or current beyond its is exhaustive, interpreted without error, or current beyond its
synthesis timestamp. publication time.
</p> </p>
<p> <p>
Nothing on the Site constitutes professional, legal, financial, Nothing on the Site constitutes professional, legal, financial,
-55
View File
@@ -1,55 +0,0 @@
import { formatRelativeTime, formatDate } from '@/lib/format';
import { safeParse } from '@/lib/llm/parse';
interface Sources {
siteName: string;
sourceUrl: string;
sourcesJson: string | null;
author?: string | null;
}
/**
* Attribution block — required for AdSense content policy.
* Every outgoing link to the original story is `rel="nofollow external"`.
*/
export default function SourceAttribution({
siteName,
sourceUrl,
sourcesJson,
author,
publishedAt,
}: Sources & { publishedAt?: string | null }) {
const sources = sourcesJson ? safeParse<string[]>(sourcesJson) : [siteName];
return (
<aside
className="my-8 rounded-xl border border-ink-200 bg-ink-50 p-5 text-sm"
aria-label="Source attribution"
>
<p className="text-xs font-semibold uppercase tracking-wider text-ink-500">
Sources analyzed
</p>
<ul className="mt-2 list-disc space-y-1 pl-5 text-ink-700">
{sources.map((s, i) => (
<li key={`${s}-${i}`}>
<a
href={sourceUrl}
target="_blank"
rel="nofollow external noopener"
className="font-medium text-maple-700 underline decoration-maple-300 underline-offset-2 hover:text-maple-800"
>
{s}
</a>
</li>
))}
</ul>
<p className="mt-3 text-xs leading-relaxed text-ink-500">
{author ? `${author} · ` : ''}
{publishedAt
? `Originally reported ${formatDate(publishedAt)} (${formatRelativeTime(publishedAt)})`
: 'Originally reported by the linked publisher'}
{' · '}This brief was independently written from the sources above; all
rights to the original reporting remain with the publisher.
</p>
</aside>
);
}
+3 -3
View File
@@ -9,8 +9,8 @@ export default function Footer() {
<div> <div>
<p className="font-display text-lg font-bold text-white">{env.siteName}</p> <p className="font-display text-lg font-bold text-white">{env.siteName}</p>
<p className="mt-2 text-sm leading-relaxed text-ink-400"> <p className="mt-2 text-sm leading-relaxed text-ink-400">
An independent, synthesized digest of Canadian headlines. Every brief is An independent digest of Canadian headlines. Every brief is a
rewritten as original reporting from the linked sources, with full rewritten, in-depth version of the reported story, with full
attribution. attribution.
</p> </p>
</div> </div>
@@ -38,7 +38,7 @@ export default function Footer() {
</div> </div>
<div className="border-t border-ink-800/70"> <div className="border-t border-ink-800/70">
<div className="mx-auto max-w-6xl px-4 py-4 text-xs text-ink-500 sm:px-6"> <div className="mx-auto max-w-6xl px-4 py-4 text-xs text-ink-500 sm:px-6">
© {year} {env.siteName}. Headlines synthesized from public news feeds. © {year} {env.siteName}. Briefs compiled from public news feeds.
All rights to original content remain with the respective publishers. All rights to original content remain with the respective publishers.
</div> </div>
</div> </div>
+1 -1
View File
@@ -21,7 +21,7 @@ export default function Header() {
{env.siteName} {env.siteName}
</span> </span>
<span className="text-[10px] uppercase tracking-[0.2em] text-ink-300"> <span className="text-[10px] uppercase tracking-[0.2em] text-ink-300">
Canada, synthesized Canada, brief by
</span> </span>
</span> </span>
</Link> </Link>
+1 -1
View File
@@ -12,7 +12,7 @@ export class OllamaProvider implements LlmProvider {
constructor( constructor(
private readonly model: string, private readonly model: string,
private readonly baseURL: string = 'http://localhost:11434', private readonly baseURL: string = 'http://localhost:11434',
private readonly maxTokens = 1200, private readonly maxTokens = 1600,
) {} ) {}
describeModel() { describeModel() {
+10 -6
View File
@@ -9,19 +9,23 @@ export const DEFAULT_ASSOCIATED_TYPE = {
ollama: env.ollamaModel, ollama: env.ollamaModel,
} as const; } as const;
export const DEFAULT_SYNTHESIS_PROMPT = `You are a Canadian newsroom synthesis editor. Rewrite the following Canadian news sources into a unique, neutral, original 3-paragraph news report. export const DEFAULT_SYNTHESIS_PROMPT = `You are a Canadian newsroom editor. Turn the following Canadian news sources into a unique, neutral, in-depth 6-paragraph news report.
Rules: Rules:
- Write in clear, neutral English. No hype, no editorializing, no sensationalism. - Write in clear, neutral English. No hype, no editorializing, no sensationalism.
- Do NOT copy sentences verbatim from the sources. Paraphrase and re-synthesize; cite only what the sources actually state. - Do NOT copy sentences verbatim from the sources. Paraphrase and rewrite; cite only what the sources actually state.
- Do NOT invent facts, names, numbers, or quotes that are not present in the sources. - Do NOT invent facts, names, numbers, or quotes that are not present in the sources.
- Paragraph 1: the core of the story (who/what/where/when). - Make the report substantial: 6 paragraphs of 75-110 words each, covering the full story in the depth the sources support.
- Paragraph 2: context, background, and reactions from the sources. - Paragraph 1: the core of the story (who/what/where/when) and why it matters.
- Paragraph 3: what happens next / implications grounded in the sources. - Paragraph 2: the key details, figures, and claims from the sources.
- Paragraph 3: reactions, quotes, and responses from the sources.
- Paragraph 4: background and context.
- Paragraph 5: what happens next, grounded in the sources.
- Paragraph 6: wider implications, grounded in the sources.
- Respond with STRICT JSON only, no markdown fences, matching this shape: - Respond with STRICT JSON only, no markdown fences, matching this shape:
{ {
"headline": "A catchy but accurate original headline (max 100 chars)", "headline": "A catchy but accurate original headline (max 100 chars)",
"body": "Paragraph 1\\n\\nParagraph 2\\n\\nParagraph 3", "body": "Paragraph 1\\n\\nParagraph 2\\n\\nParagraph 3\\n\\nParagraph 4\\n\\nParagraph 5\\n\\nParagraph 6",
"takeaways": ["short factual takeaway 1", "short factual takeaway 2", "short factual takeaway 3"], "takeaways": ["short factual takeaway 1", "short factual takeaway 2", "short factual takeaway 3"],
"tags": ["tag1", "tag2", "tag3"] "tags": ["tag1", "tag2", "tag3"]
} }