Skip to content

Commit d0d00b1

Browse files
authored
feat(changelog): publish curated product updates (#8787)
* feat(changelog): publish curated product updates * fix(changelog): harden feed rendering and media validation * improvement(changelog): present updates in a crawlable list * improvement(changelog): define the Sim editorial workflow Document scheduled discovery, durable candidate state, draft PR preparation, real-media review, and operational checks. Reuse the existing cached checkout for trusted Helm PR checks while preserving full history. * fix(changelog): preserve editorial decisions in the workflow plan * docs(changelog): refine product stories and define editorial quality * refactor(changelog): simplify presentation and validate model links
1 parent 57c227f commit d0d00b1

70 files changed

Lines changed: 1718 additions & 466 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎.github/PULL_REQUEST_TEMPLATE.md‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,9 @@ Brief description of what this PR does and why.
33

44
Fixes #(issue)
55

6+
## Product update (if relevant)
7+
Who benefits, and what can they do now? Note rollout or plan restrictions, link the relevant Sim integration or docs guide, and include a real product demo or changelog production brief when available.
8+
69
## Type of Change
710
- [ ] Bug fix
811
- [ ] New feature

‎.github/workflows/helm.yml‎

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -37,12 +37,17 @@ jobs:
3737
runs-on: &runner-2vcpu ${{ (vars.CI_PROVIDER == '' || vars.CI_PROVIDER == 'blacksmith') && 'blacksmith-2vcpu-ubuntu-2404' || 'ubuntu-latest' }}
3838
timeout-minutes: 15
3939
steps:
40-
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v6
41-
with:
42-
# ct diffs the chart against the PR base to decide whether the version
43-
# was bumped, so a shallow clone would leave it nothing to compare.
40+
- name: Checkout code (mirror)
41+
if: github.event_name == 'pull_request' && !github.event.pull_request.head.repo.fork
42+
uses: useblacksmith/checkout@25227e61ff9dafe400e22fa487b673eac4e4409a # v1.8.1
43+
with: &chart-checkout-options
44+
# Chart version checks need full history to compare against the PR base.
4445
fetch-depth: 0
4546
persist-credentials: false
47+
- name: Checkout code
48+
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.fork
49+
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v6
50+
with: *chart-checkout-options
4651

4752
- name: Set up Helm
4853
uses: azure/setup-helm@9bc31f4ebc9c6b171d7bfbaa5d006ae7abdb4310 # v5.0.1

‎apps/sim/AGENTS.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ For a common task, start from its skill (`.agents/skills/<name>/SKILL.md`):
2121

2222
<!-- BEGIN:nextjs-agent-rules -->
2323

24-
# This is NOT the Next.js you know
24+
## This is NOT the Next.js you know
2525

2626
This version has breaking changes — APIs, conventions, and file structure may all differ from your training data. Read the relevant guide in `node_modules/next/dist/docs/` (resolved from this file's directory; in monorepos the `next` package may not be visible from the repo root) before writing any code. Heed deprecation notices.
2727

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import type { Metadata } from 'next'
2+
import { notFound } from 'next/navigation'
3+
import { CHANGELOG_SECTION, getAllEntryMeta, getEntryBySlug } from '@/lib/changelog'
4+
import { buildPostGraphJsonLd, buildPostMetadata } from '@/lib/content/seo'
5+
import { ChangelogArticle, ChangelogLayout } from '@/app/(landing)/changelog/components'
6+
import { JsonLd } from '@/app/(landing)/components/json-ld'
7+
8+
export const dynamicParams = false
9+
export const revalidate = 3600
10+
11+
interface ChangelogEntryPageProps {
12+
params: Promise<{ slug: string }>
13+
}
14+
15+
export async function generateStaticParams() {
16+
return (await getAllEntryMeta()).map((entry) => ({ slug: entry.slug }))
17+
}
18+
19+
export async function generateMetadata({ params }: ChangelogEntryPageProps): Promise<Metadata> {
20+
const { slug } = await params
21+
const entry = await getEntryBySlug(slug)
22+
return entry ? buildPostMetadata(entry) : {}
23+
}
24+
25+
export default async function ChangelogEntryPage({ params }: ChangelogEntryPageProps) {
26+
const { slug } = await params
27+
const entry = await getEntryBySlug(slug)
28+
if (!entry) notFound()
29+
30+
return (
31+
<ChangelogLayout>
32+
<JsonLd data={buildPostGraphJsonLd(entry, CHANGELOG_SECTION)} />
33+
<ChangelogArticle entry={entry} />
34+
</ChangelogLayout>
35+
)
36+
}
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
import { CHANGELOG_SECTION, getAllEntryMeta } from '@/lib/changelog'
2+
import { buildCollectionPageJsonLd } from '@/lib/content/seo'
3+
import { buildLandingMetadata } from '@/lib/landing/seo'
4+
import {
5+
ChangelogHeader,
6+
ChangelogLayout,
7+
ChangelogList,
8+
} from '@/app/(landing)/changelog/components'
9+
import { BackLink } from '@/app/(landing)/components/back-link'
10+
import { JsonLd } from '@/app/(landing)/components/json-ld'
11+
12+
export const revalidate = 3600
13+
export const metadata = buildLandingMetadata({
14+
title: 'Changelog Archive | Sim',
15+
description: 'Browse product updates from Sim and find the full technical release history.',
16+
path: '/changelog/archive',
17+
})
18+
19+
export default async function ChangelogArchivePage() {
20+
const entries = await getAllEntryMeta()
21+
return (
22+
<ChangelogLayout>
23+
<JsonLd
24+
data={buildCollectionPageJsonLd(
25+
{ ...CHANGELOG_SECTION, name: 'Changelog Archive', basePath: '/changelog/archive' },
26+
entries
27+
)}
28+
/>
29+
<ChangelogHeader
30+
title='All updates'
31+
actions={<BackLink href='/changelog' label='Back to changelog' />}
32+
/>
33+
<ChangelogList entries={entries} />
34+
<a
35+
href='https://github.com/simstudioai/sim/releases'
36+
target='_blank'
37+
rel='noopener noreferrer'
38+
className='text-[var(--text-secondary)] text-small underline underline-offset-4'
39+
>
40+
Earlier releases and full technical history on GitHub
41+
</a>
42+
</ChangelogLayout>
43+
)
44+
}
Lines changed: 36 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -1,50 +1,41 @@
1-
import { createLogger } from '@sim/logger'
2-
import { getErrorMessage } from '@sim/utils/errors'
3-
import { ChangelogActions, ChangelogTimeline } from '@/app/(landing)/changelog/components'
4-
import type { ChangelogEntry, GitHubRelease } from '@/app/(landing)/changelog/types'
5-
import { mapReleases, releasesEndpoint } from '@/app/(landing)/changelog/utils'
6-
import { ProseHero, ProseShell } from '@/app/(landing)/components/prose-page'
7-
8-
const logger = createLogger('Changelog')
9-
10-
/**
11-
* Changelog page - reuses the shared prose primitives ({@link ProseShell} +
12-
* {@link ProseHero}) so its headline and column match Terms and Privacy, then
13-
* renders the GitHub-release timeline. The first page of releases is
14-
* fetched here on the server at build/revalidate time; the {@link ChangelogTimeline}
15-
* client leaf paginates the rest. Re-authored from the prior dark changelog onto
16-
* the platform light tokens.
17-
*/
18-
19-
const LEAD =
20-
'Every new feature, improvement, and fix in Sim, the open-source AI workspace, with release notes straight from GitHub.'
21-
22-
async function getInitialEntries(): Promise<ChangelogEntry[]> {
23-
try {
24-
// boundary-raw-fetch: external GitHub Releases API (cross-origin), not a same-origin contract
25-
const res = await fetch(releasesEndpoint(1), {
26-
headers: { Accept: 'application/vnd.github+json' },
27-
next: { revalidate: 3600 },
28-
})
29-
const releases = (await res.json()) as GitHubRelease[]
30-
return mapReleases(releases ?? [])
31-
} catch (error) {
32-
logger.warn('Failed to load initial changelog releases from GitHub', {
33-
error: getErrorMessage(error),
34-
})
35-
return []
36-
}
37-
}
1+
import Link from 'next/link'
2+
import { CHANGELOG_SECTION, getAllEntryMeta, LATEST_ENTRY_LIMIT } from '@/lib/changelog'
3+
import { buildCollectionPageJsonLd } from '@/lib/content/seo'
4+
import {
5+
ChangelogActions,
6+
ChangelogHeader,
7+
ChangelogLayout,
8+
ChangelogList,
9+
} from '@/app/(landing)/changelog/components'
10+
import { JsonLd } from '@/app/(landing)/components/json-ld'
3811

3912
export default async function Changelog() {
40-
const entries = await getInitialEntries()
41-
13+
const metadata = await getAllEntryMeta()
14+
const visible = metadata.slice(0, LATEST_ENTRY_LIMIT)
15+
const firstOlderEntry = metadata[LATEST_ENTRY_LIMIT]
4216
return (
43-
<ProseShell>
44-
<ProseHero title='Changelog' lead={LEAD} actions={<ChangelogActions />} />
45-
<section id='releases' aria-label='Release history'>
46-
<ChangelogTimeline initialEntries={entries} />
47-
</section>
48-
</ProseShell>
17+
<ChangelogLayout>
18+
<JsonLd data={buildCollectionPageJsonLd(CHANGELOG_SECTION, visible)} />
19+
<ChangelogHeader title='Changelog' actions={<ChangelogActions />} />
20+
<ChangelogList entries={visible} />
21+
<div className='flex flex-wrap gap-6 text-[var(--text-secondary)] text-sm'>
22+
{firstOlderEntry ? (
23+
<Link
24+
href={`/changelog/archive#${firstOlderEntry.slug}`}
25+
className='underline underline-offset-4'
26+
>
27+
Older updates
28+
</Link>
29+
) : null}
30+
<a
31+
href='https://github.com/simstudioai/sim/releases'
32+
target='_blank'
33+
rel='noopener noreferrer'
34+
className='underline underline-offset-4'
35+
>
36+
Earlier releases on GitHub
37+
</a>
38+
</div>
39+
</ChangelogLayout>
4940
)
5041
}

‎apps/sim/app/(landing)/changelog/components/changelog-actions/changelog-actions.tsx‎

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,20 +9,18 @@ import { GithubOutlineIcon } from '@/components/icons'
99
* beneath the changelog headline. A small client leaf because `ChipLink` is a
1010
* Client Component and its `leftIcon` is a component reference that cannot cross
1111
* the server→client boundary as a prop (same pattern as the platform pill CTA).
12-
* GitHub is the primary filled chip; Docs and RSS are the default pills.
1312
*/
1413
export function ChangelogActions() {
1514
return (
1615
<div className='flex flex-wrap items-center gap-1'>
1716
<ChipLink
18-
variant='primary'
1917
shape='round'
2018
href='https://github.com/simstudioai/sim/releases'
2119
target='_blank'
2220
rel='noopener noreferrer'
2321
leftIcon={GithubOutlineIcon}
2422
>
25-
View on GitHub
23+
Technical releases
2624
</ChipLink>
2725
<ChipLink
2826
shape='round'
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
import { cn } from '@sim/emcn'
2+
import type { ContentPost } from '@/lib/content/schema'
3+
import { BackLink } from '@/app/(landing)/components/back-link'
4+
import { formatPostDate } from '@/app/(landing)/components/content-utils'
5+
import { HOME_TYPE, LANDING_TYPE } from '@/app/(landing)/components/landing-layout'
6+
7+
interface ChangelogArticleProps {
8+
entry: ContentPost
9+
backHref?: string
10+
archiveHref?: string
11+
}
12+
13+
/** A focused reading column contains the update's product recordings and release details. */
14+
export function ChangelogArticle({
15+
entry,
16+
backHref = '/changelog',
17+
archiveHref = '/changelog/archive',
18+
}: ChangelogArticleProps) {
19+
const updated =
20+
entry.updated && entry.updated.slice(0, 10) !== entry.date.slice(0, 10)
21+
? entry.updated
22+
: undefined
23+
return (
24+
<article
25+
id={entry.slug}
26+
className='mx-auto w-full max-w-[900px]'
27+
itemScope
28+
itemType='https://schema.org/BlogPosting'
29+
>
30+
<header className='flex flex-col items-start gap-6'>
31+
<BackLink href={backHref} label='Back to changelog' />
32+
<div className='flex flex-wrap items-center gap-x-4 gap-y-2 text-[var(--text-secondary)] text-sm'>
33+
{entry.draft ? <span>Draft</span> : null}
34+
<time dateTime={entry.date}>{formatPostDate(entry.date)}</time>
35+
{entry.release ? <span>{entry.release.versions.join(' · ')}</span> : null}
36+
{updated ? <time dateTime={entry.updated}>Updated {formatPostDate(updated)}</time> : null}
37+
</div>
38+
<h1
39+
itemProp='headline'
40+
className={cn('text-balance text-[var(--text-primary)]', LANDING_TYPE.proof)}
41+
>
42+
{entry.title}
43+
</h1>
44+
</header>
45+
<section id='update-details' aria-labelledby='update-details-heading' className='mt-8'>
46+
<h2 id='update-details-heading' className='sr-only'>
47+
In this update
48+
</h2>
49+
<entry.Content />
50+
</section>
51+
<footer
52+
className={cn('mt-10 flex flex-wrap gap-6 text-[var(--text-secondary)]', HOME_TYPE.meta)}
53+
>
54+
{entry.release?.url ? (
55+
<a
56+
href={entry.release.url}
57+
target='_blank'
58+
rel='noopener noreferrer'
59+
className='underline underline-offset-4'
60+
>
61+
Technical release notes
62+
</a>
63+
) : null}
64+
<BackLink href={archiveHref} label='All updates' />
65+
</footer>
66+
</article>
67+
)
68+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export { ChangelogArticle } from './changelog-article'
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import type { ReactNode } from 'react'
2+
import { cn } from '@sim/emcn'
3+
import { LANDING_TYPE } from '@/app/(landing)/components/landing-layout'
4+
5+
interface ChangelogHeaderProps {
6+
title: string
7+
lead?: string
8+
actions?: ReactNode
9+
}
10+
11+
export function ChangelogHeader({ title, lead, actions }: ChangelogHeaderProps) {
12+
return (
13+
<header className='flex flex-col items-start gap-6'>
14+
<h1 className={cn('text-[var(--text-primary)]', LANDING_TYPE.h1)}>{title}</h1>
15+
{lead ? (
16+
<p className={cn('max-w-[640px] text-[var(--text-secondary)]', LANDING_TYPE.lead)}>
17+
{lead}
18+
</p>
19+
) : null}
20+
{actions}
21+
</header>
22+
)
23+
}

0 commit comments

Comments
 (0)