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: 1 addition & 1 deletion apps/desktop/src/changelog/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,7 @@ function ChangelogHeader({
date: string | null;
}) {
const formattedDate = date ? safeFormat(date, "MMM d, yyyy") : null;
const webUrl = `https://char.com/changelog/${version}`;
const webUrl = `https://anarlog.so/changelog/${version}`;

return (
<div className="w-full pt-1">
Expand Down
3 changes: 3 additions & 0 deletions apps/web/src/components/site-footer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@ export function SiteFooter() {
<Link to="/blog/" className="hover:text-[#181613]">
Blog
</Link>
<Link to="/changelog/" className="hover:text-[#181613]">
Changelog
</Link>
<a href="mailto:founders@fastrepl.com" className="hover:text-[#181613]">
Contact
</a>
Expand Down
60 changes: 60 additions & 0 deletions apps/web/src/lib/changelog.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import { processContent } from "@hypr/changelog";

const rawEntries = import.meta.glob(
"../../../../packages/changelog/content/*.md",
{
eager: true,
import: "default",
query: "?raw",
},
) as Record<string, string>;
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AGENTS.md exposed as a public changelog entry

High Severity

The glob *.md in changelog.ts and getChangelogVersions in sitemap.ts both pick up the AGENTS.md file from packages/changelog/content/, which contains internal AI agent instructions — not a release. It passes the .filter((entry) => entry.version) check because "AGENTS" is truthy. This creates a public /changelog/AGENTS page exposing development instructions and adds it to the sitemap. Additionally, compareVersionsDesc receives NaN from "AGENTS".split(".").map(Number), causing undefined sort behavior for the entire list.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 42af488. Configure here.


export const changelogEntries = Object.entries(rawEntries)
.map(([filePath, raw]) => {
const version = filePath.split("/").pop()?.replace(/\.md$/, "") ?? "";
const { content, date } = processContent(raw);

return {
version,
content,
date: normalizeDate(date),
};
})
.filter((entry) => entry.version)
.sort((a, b) => compareVersionsDesc(a.version, b.version));

export function getChangelogEntry(version: string) {
return changelogEntries.find((entry) => entry.version === version);
}

function normalizeDate(date: string | null) {
return date?.replace(/^["']|["']$/g, "") ?? null;
}

function compareVersionsDesc(a: string, b: string) {
const left = a.split(".").map(Number);
const right = b.split(".").map(Number);
const length = Math.max(left.length, right.length);

for (let i = 0; i < length; i++) {
const diff = (right[i] ?? 0) - (left[i] ?? 0);
if (diff !== 0) return diff;
}

return b.localeCompare(a);
}

export function formatChangelogDate(date: string) {
const parsed = new Date(`${date}T00:00:00Z`);

if (Number.isNaN(parsed.getTime())) {
return date;
}

return new Intl.DateTimeFormat("en-US", {
month: "short",
day: "numeric",
year: "numeric",
timeZone: "UTC",
}).format(parsed);
}
42 changes: 42 additions & 0 deletions apps/web/src/routeTree.gen.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,10 @@ import { Route as FoundersRouteImport } from './routes/founders'
import { Route as AuthRouteImport } from './routes/auth'
import { Route as ViewRouteRouteImport } from './routes/_view/route'
import { Route as IndexRouteImport } from './routes/index'
import { Route as ChangelogIndexRouteImport } from './routes/changelog/index'
import { Route as BlogIndexRouteImport } from './routes/blog/index'
import { Route as LegalSlugRouteImport } from './routes/legal/$slug'
import { Route as ChangelogVersionRouteImport } from './routes/changelog/$version'
import { Route as BlogSlugRouteImport } from './routes/blog/$slug'
import { Route as ApiTemplatesRouteImport } from './routes/api/templates'
import { Route as ApiShortcutsRouteImport } from './routes/api/shortcuts'
Expand Down Expand Up @@ -103,6 +105,11 @@ const IndexRoute = IndexRouteImport.update({
path: '/',
getParentRoute: () => rootRouteImport,
} as any)
const ChangelogIndexRoute = ChangelogIndexRouteImport.update({
id: '/changelog/',
path: '/changelog/',
getParentRoute: () => rootRouteImport,
} as any)
const BlogIndexRoute = BlogIndexRouteImport.update({
id: '/blog/',
path: '/blog/',
Expand All @@ -113,6 +120,11 @@ const LegalSlugRoute = LegalSlugRouteImport.update({
path: '/legal/$slug',
getParentRoute: () => rootRouteImport,
} as any)
const ChangelogVersionRoute = ChangelogVersionRouteImport.update({
id: '/changelog/$version',
path: '/changelog/$version',
getParentRoute: () => rootRouteImport,
} as any)
const BlogSlugRoute = BlogSlugRouteImport.update({
id: '/blog/$slug',
path: '/blog/$slug',
Expand Down Expand Up @@ -415,8 +427,10 @@ export interface FileRoutesByFullPath {
'/api/shortcuts': typeof ApiShortcutsRoute
'/api/templates': typeof ApiTemplatesRoute
'/blog/$slug': typeof BlogSlugRoute
'/changelog/$version': typeof ChangelogVersionRoute
'/legal/$slug': typeof LegalSlugRoute
'/blog/': typeof BlogIndexRoute
'/changelog/': typeof ChangelogIndexRoute
'/app/account': typeof ViewAppAccountRoute
'/app/checkout': typeof ViewAppCheckoutRoute
'/app/integration': typeof ViewAppIntegrationRoute
Expand Down Expand Up @@ -479,8 +493,10 @@ export interface FileRoutesByTo {
'/api/shortcuts': typeof ApiShortcutsRoute
'/api/templates': typeof ApiTemplatesRoute
'/blog/$slug': typeof BlogSlugRoute
'/changelog/$version': typeof ChangelogVersionRoute
'/legal/$slug': typeof LegalSlugRoute
'/blog': typeof BlogIndexRoute
'/changelog': typeof ChangelogIndexRoute
'/app/account': typeof ViewAppAccountRoute
'/app/checkout': typeof ViewAppCheckoutRoute
'/app/integration': typeof ViewAppIntegrationRoute
Expand Down Expand Up @@ -546,8 +562,10 @@ export interface FileRoutesById {
'/api/shortcuts': typeof ApiShortcutsRoute
'/api/templates': typeof ApiTemplatesRoute
'/blog/$slug': typeof BlogSlugRoute
'/changelog/$version': typeof ChangelogVersionRoute
'/legal/$slug': typeof LegalSlugRoute
'/blog/': typeof BlogIndexRoute
'/changelog/': typeof ChangelogIndexRoute
'/_view/app/account': typeof ViewAppAccountRoute
'/_view/app/checkout': typeof ViewAppCheckoutRoute
'/_view/app/integration': typeof ViewAppIntegrationRoute
Expand Down Expand Up @@ -613,8 +631,10 @@ export interface FileRouteTypes {
| '/api/shortcuts'
| '/api/templates'
| '/blog/$slug'
| '/changelog/$version'
| '/legal/$slug'
| '/blog/'
| '/changelog/'
| '/app/account'
| '/app/checkout'
| '/app/integration'
Expand Down Expand Up @@ -677,8 +697,10 @@ export interface FileRouteTypes {
| '/api/shortcuts'
| '/api/templates'
| '/blog/$slug'
| '/changelog/$version'
| '/legal/$slug'
| '/blog'
| '/changelog'
| '/app/account'
| '/app/checkout'
| '/app/integration'
Expand Down Expand Up @@ -743,8 +765,10 @@ export interface FileRouteTypes {
| '/api/shortcuts'
| '/api/templates'
| '/blog/$slug'
| '/changelog/$version'
| '/legal/$slug'
| '/blog/'
| '/changelog/'
| '/_view/app/account'
| '/_view/app/checkout'
| '/_view/app/integration'
Expand Down Expand Up @@ -808,8 +832,10 @@ export interface RootRouteChildren {
ApiShortcutsRoute: typeof ApiShortcutsRoute
ApiTemplatesRoute: typeof ApiTemplatesRoute
BlogSlugRoute: typeof BlogSlugRoute
ChangelogVersionRoute: typeof ChangelogVersionRoute
LegalSlugRoute: typeof LegalSlugRoute
BlogIndexRoute: typeof BlogIndexRoute
ChangelogIndexRoute: typeof ChangelogIndexRoute
ApiAssetsSplatRoute: typeof ApiAssetsSplatRoute
ApiTweetIdRoute: typeof ApiTweetIdRoute
ApiWebhooksSlackInteractiveRoute: typeof ApiWebhooksSlackInteractiveRoute
Expand Down Expand Up @@ -890,6 +916,13 @@ declare module '@tanstack/react-router' {
preLoaderRoute: typeof IndexRouteImport
parentRoute: typeof rootRouteImport
}
'/changelog/': {
id: '/changelog/'
path: '/changelog'
fullPath: '/changelog/'
preLoaderRoute: typeof ChangelogIndexRouteImport
parentRoute: typeof rootRouteImport
}
'/blog/': {
id: '/blog/'
path: '/blog'
Expand All @@ -904,6 +937,13 @@ declare module '@tanstack/react-router' {
preLoaderRoute: typeof LegalSlugRouteImport
parentRoute: typeof rootRouteImport
}
'/changelog/$version': {
id: '/changelog/$version'
path: '/changelog/$version'
fullPath: '/changelog/$version'
preLoaderRoute: typeof ChangelogVersionRouteImport
parentRoute: typeof rootRouteImport
}
'/blog/$slug': {
id: '/blog/$slug'
path: '/blog/$slug'
Expand Down Expand Up @@ -1366,8 +1406,10 @@ const rootRouteChildren: RootRouteChildren = {
ApiShortcutsRoute: ApiShortcutsRoute,
ApiTemplatesRoute: ApiTemplatesRoute,
BlogSlugRoute: BlogSlugRoute,
ChangelogVersionRoute: ChangelogVersionRoute,
LegalSlugRoute: LegalSlugRoute,
BlogIndexRoute: BlogIndexRoute,
ChangelogIndexRoute: ChangelogIndexRoute,
ApiAssetsSplatRoute: ApiAssetsSplatRoute,
ApiTweetIdRoute: ApiTweetIdRoute,
ApiWebhooksSlackInteractiveRoute: ApiWebhooksSlackInteractiveRoute,
Expand Down
89 changes: 89 additions & 0 deletions apps/web/src/routes/changelog/$version.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
import { createFileRoute, Link, notFound } from "@tanstack/react-router";

import { ChangelogContent } from "@hypr/changelog";

import { SiteFooter } from "@/components/site-footer";
import { formatChangelogDate, getChangelogEntry } from "@/lib/changelog";
import { ANARLOG_SITE_URL } from "@/lib/seo";

export const Route = createFileRoute("/changelog/$version")({
component: Component,
loader: async ({ params }) => {
const entry = getChangelogEntry(params.version);
if (!entry) {
throw notFound();
}
return { entry };
},
head: ({ loaderData }) => {
const entry = loaderData?.entry;
if (!entry) return {};

const url = `${ANARLOG_SITE_URL}/changelog/${entry.version}`;
return {
links: [{ rel: "canonical", href: url }],
meta: [
{ title: `Anarlog v${entry.version} Changelog` },
{
name: "description",
content: `Release notes for Anarlog v${entry.version}.`,
},
{
property: "og:title",
content: `Anarlog v${entry.version} Changelog`,
},
{
property: "og:description",
content: `Release notes for Anarlog v${entry.version}.`,
},
{ property: "og:url", content: url },
],
};
},
});

function Component() {
const { entry } = Route.useLoaderData();

return (
<main className="min-h-screen bg-white text-[#181613]">
<div className="mx-auto w-full max-w-[700px] px-5 py-8 md:px-8 md:py-12">
<header className="flex items-center justify-between gap-6">
<Link to="/" aria-label="Anarlog home">
<img src="/logo.svg" alt="Anarlog" className="h-9 w-auto" />
</Link>
</header>

<Link
to="/changelog/"
className="mt-16 inline-block text-sm text-[#756b5d] hover:text-[#181613]"
>
← Changelog
</Link>

<header className="pt-10 pb-12">
<h1 className="font-hand text-5xl leading-[1.02] font-semibold tracking-normal text-balance text-black md:text-7xl">
v{entry.version}
</h1>
{entry.date && (
<time
dateTime={entry.date}
className="mt-6 block text-sm text-[#756b5d]"
>
{formatChangelogDate(entry.date)}
</time>
)}
</header>

<article className="border-t border-[#eee8df] pt-8">
<ChangelogContent
content={entry.content}
className="text-sm leading-7"
/>
</article>
</div>

<SiteFooter />
</main>
);
}
Loading
Loading