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
107 changes: 107 additions & 0 deletions src/app/(public)/changelog/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
import React from 'react';
import { PageHeading } from '@/components/layout/PageHeading';
import { CHANGELOG, CHANGELOG_TAGS, type ChangelogTag } from '@/config/changelog';

export const metadata = {
title: 'Changelog',
description:
'What’s new on OrangeCat — every update that shipped, newest first. Bitcoin-native funding, discovery, and the sovereign personal economy, in the open.',
};

const MONTHS = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];

/** Format 'YYYY-MM-DD' → 'Jul 22, 2026' deterministically (no TZ / hydration drift). */
function formatDate(iso: string): string {
const [y, m, d] = iso.split('-').map(Number);
return `${MONTHS[(m ?? 1) - 1]} ${d}, ${y}`;
}

// Warm accent for the notable tags (new features, platform), monochrome for the rest.
const TAG_CHIP: Record<ChangelogTag, string> = {
feature: 'border-accent-warm text-accent-warm',
platform: 'border-accent-warm text-accent-warm',
improvement: 'border-default text-fg-secondary',
fix: 'border-default text-fg-tertiary',
};
const TAG_DOT: Record<ChangelogTag, string> = {
feature: 'bg-accent-warm',
platform: 'bg-accent-warm',
improvement: 'bg-strong',
fix: 'bg-strong',
};

/**
* /changelog — public product changelog (no login required). A curated,
* chronological record of what shipped, styled as a quiet timeline: date +
* tag on the left rail, the human-readable change on the right. Content SSOT
* lives in `src/config/changelog.ts`.
*/
export default function ChangelogPage() {
return (
<div className="min-h-screen bg-surface-page">
<div className="mx-auto max-w-3xl px-4 py-12 sm:px-6 lg:px-8">
{/* Hero */}
<div className="mb-14">
<PageHeading className="mb-3">Changelog</PageHeading>
<p className="max-w-2xl text-lg text-fg-secondary">
What’s new on OrangeCat — building the Bitcoin-native personal economy in the open.
Newest first.
</p>
</div>

{/* Timeline */}
<ol className="relative space-y-12 border-l border-default pl-6 sm:pl-8">
{CHANGELOG.map((entry, i) => (
<li key={`${entry.date}-${i}`} className="relative">
{/* Dot on the rail */}
<span
aria-hidden
className={`absolute -left-[calc(0.25rem+1px)] top-1.5 h-2 w-2 -translate-x-1/2 rounded-full ring-4 ring-surface-page sm:-left-[calc(0.5rem+1px)] ${TAG_DOT[entry.tag]}`}
/>

<div className="mb-2 flex flex-wrap items-center gap-x-3 gap-y-1">
<time dateTime={entry.date} className="font-mono text-xs text-fg-tertiary">
{formatDate(entry.date)}
</time>
<span
className={`inline-flex items-center rounded-full border px-2 py-0.5 text-2xs font-medium uppercase tracking-wide ${TAG_CHIP[entry.tag]}`}
>
{CHANGELOG_TAGS[entry.tag].label}
</span>
</div>

<h2 className="text-xl font-bold tracking-display text-fg-primary">{entry.title}</h2>
<p className="mt-1.5 text-fg-secondary">{entry.summary}</p>

{entry.items && entry.items.length > 0 && (
<ul className="mt-3 space-y-1.5">
{entry.items.map((item, j) => (
<li key={j} className="flex gap-2.5 text-sm text-fg-secondary">
<span
aria-hidden
className="mt-2 h-1 w-1 flex-shrink-0 rounded-full bg-strong"
/>
<span>{item}</span>
</li>
))}
</ul>
)}
</li>
))}
</ol>

{/* Foot note */}
<p className="mt-16 border-t border-default pt-8 text-sm text-fg-tertiary">
Everything here shipped to production. Follow along —{' '}
<a
href="/discover"
className="text-fg-secondary underline-offset-4 hover:text-fg-primary hover:underline"
>
explore what people are building
</a>
.
</p>
</div>
</div>
);
}
122 changes: 122 additions & 0 deletions src/config/changelog.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
/**
* Public product changelog — SSOT.
*
* A curated, human-readable record of what shipped, newest first. Rendered at
* the public `/changelog` route (no login required). Keep entries user-facing:
* describe the change from the reader's side, not the commit. Every entry here
* corresponds to work that actually shipped to production.
*/

export type ChangelogTag = 'feature' | 'improvement' | 'fix' | 'platform';

export interface ChangelogEntry {
/** ISO date the change shipped (YYYY-MM-DD). */
date: string;
tag: ChangelogTag;
title: string;
summary: string;
/** Optional detail bullets. */
items?: string[];
}

/** Display metadata for each tag (label + which token drives its chip). */
export const CHANGELOG_TAGS: Record<ChangelogTag, { label: string }> = {
feature: { label: 'New' },
improvement: { label: 'Improved' },
fix: { label: 'Fixed' },
platform: { label: 'Platform' },
};

/** Newest first. */
export const CHANGELOG: ChangelogEntry[] = [
{
date: '2026-07-22',
tag: 'feature',
title: 'Discover, sharper and searchable',
summary:
'Find what matters by meaning, not just keywords — and let search engines find it too.',
items: [
'Public semantic search across the economy: projects, people, and offers, matched by intent.',
'Discover now server-renders a crawlable content strip, so published work is indexable and shareable.',
],
},
{
date: '2026-07-21',
tag: 'feature',
title: 'A two-sided market',
summary:
'The platform now indexes what people want, not only what they offer — and introduces the two sides.',
items: [
'A public open-demand feed: browse what the community is looking for.',
'Two-sided introductions surface matches between demand and supply.',
],
},
{
date: '2026-07-21',
tag: 'feature',
title: 'Support in Bitcoin',
summary:
'A Bitcoin-native Supporter checkout — back the platform directly, settled on Lightning or on-chain.',
},
{
date: '2026-07-19',
tag: 'platform',
title: 'Wired to FleetCrown',
summary: 'OrangeCat is the economic layer for FleetCrown — and now the wiring runs both ways.',
items: [
'Settled funding on a project or cause signals the FleetCrown fleet automatically.',
'Embedded the FleetCrown feedback widget — OrangeCat runs as its own second customer.',
],
},
{
date: '2026-07-14',
tag: 'improvement',
title: 'Cat Credits go-live plumbing + steadier sessions',
summary:
'Groundwork for paid Cat Credits, and a fix so auth-gated pages resolve instead of spinning.',
items: [
'Cat Credits go-live is now environment-driven, with a wallet-verification step.',
'Closed a hydration gap that could pin logged-in pages on a loading spinner.',
],
},
{
date: '2026-07-13',
tag: 'feature',
title: 'Cat-first, and payable on publish',
summary:
'Your Cat is the front door, and publishing something payable is a guided, one-link step.',
items: [
'The dashboard routes everyone to the Cat hub.',
'Publishing a payable entity nudges you to connect a wallet and hands you a payable public link.',
'Hardened a public-profile data-exposure path.',
],
},
{
date: '2026-07-09',
tag: 'feature',
title: 'Peer-to-peer Bitcoin loans',
summary:
'Lend and borrow directly: offers, obligations, and payment handoffs — no bank in the middle.',
},
{
date: '2026-07-08',
tag: 'improvement',
title: 'Funding transparency you control',
summary:
'Per-entity controls over how much of your funding picture is public — transparency by choice.',
},
{
date: '2026-06-17',
tag: 'platform',
title: 'Log in with OrangeCat',
summary:
'OrangeCat became an OpenID Connect provider — one identity you can carry across the ecosystem.',
},
{
date: '2026-06-12',
tag: 'platform',
title: 'Self-hosted and sovereign',
summary:
'Moved to fully self-hosted infrastructure — the platform runs on hardware we control.',
},
];
1 change: 1 addition & 0 deletions src/config/navigation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -296,6 +296,7 @@ export const footerNavigation = {
product: [
{ name: 'Documentation', href: ROUTES.DOCS },
{ name: 'How It Works', href: ROUTES.HOW_IT_WORKS },
{ name: 'Changelog', href: ROUTES.CHANGELOG },
{ name: 'Status', href: ROUTES.STATUS },
],
company: [
Expand Down
1 change: 1 addition & 0 deletions src/config/routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -280,6 +280,7 @@ export const ROUTES = {
ABOUT: '/about',
HOW_IT_WORKS: '/how-it-works',
BLOG: '/blog',
CHANGELOG: '/changelog',
DOCS: '/docs',
FAQ: '/faq',
PRICING: '/pricing',
Expand Down
Loading