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
3 changes: 3 additions & 0 deletions .github/PULL_REQUEST_TEMPLATE.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@ Brief description of what this PR does and why.

Fixes #(issue)

## Product update (if relevant)
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.

## Type of Change
- [ ] Bug fix
- [ ] New feature
Expand Down
13 changes: 9 additions & 4 deletions .github/workflows/helm.yml
Original file line number Diff line number Diff line change
Expand Up @@ -37,12 +37,17 @@ jobs:
runs-on: &runner-2vcpu ${{ (vars.CI_PROVIDER == '' || vars.CI_PROVIDER == 'blacksmith') && 'blacksmith-2vcpu-ubuntu-2404' || 'ubuntu-latest' }}
timeout-minutes: 15
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v6
with:
# ct diffs the chart against the PR base to decide whether the version
# was bumped, so a shallow clone would leave it nothing to compare.
- name: Checkout code (mirror)
if: github.event_name == 'pull_request' && !github.event.pull_request.head.repo.fork
uses: useblacksmith/checkout@25227e61ff9dafe400e22fa487b673eac4e4409a # v1.8.1
with: &chart-checkout-options
# Chart version checks need full history to compare against the PR base.
fetch-depth: 0
persist-credentials: false
- name: Checkout code
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.fork
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v6
with: *chart-checkout-options

- name: Set up Helm
uses: azure/setup-helm@9bc31f4ebc9c6b171d7bfbaa5d006ae7abdb4310 # v5.0.1
Expand Down
2 changes: 1 addition & 1 deletion apps/sim/AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ For a common task, start from its skill (`.agents/skills/<name>/SKILL.md`):

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

# This is NOT the Next.js you know
## This is NOT the Next.js you know

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.

Expand Down
36 changes: 36 additions & 0 deletions apps/sim/app/(landing)/changelog/[slug]/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import type { Metadata } from 'next'
import { notFound } from 'next/navigation'
import { CHANGELOG_SECTION, getAllEntryMeta, getEntryBySlug } from '@/lib/changelog'
import { buildPostGraphJsonLd, buildPostMetadata } from '@/lib/content/seo'
import { ChangelogArticle, ChangelogLayout } from '@/app/(landing)/changelog/components'
import { JsonLd } from '@/app/(landing)/components/json-ld'

export const dynamicParams = false
export const revalidate = 3600

interface ChangelogEntryPageProps {
params: Promise<{ slug: string }>
}

export async function generateStaticParams() {
return (await getAllEntryMeta()).map((entry) => ({ slug: entry.slug }))
}

export async function generateMetadata({ params }: ChangelogEntryPageProps): Promise<Metadata> {
const { slug } = await params
const entry = await getEntryBySlug(slug)
return entry ? buildPostMetadata(entry) : {}
}

export default async function ChangelogEntryPage({ params }: ChangelogEntryPageProps) {
const { slug } = await params
const entry = await getEntryBySlug(slug)
if (!entry) notFound()

return (
<ChangelogLayout>
<JsonLd data={buildPostGraphJsonLd(entry, CHANGELOG_SECTION)} />
<ChangelogArticle entry={entry} />
</ChangelogLayout>
)
}
44 changes: 44 additions & 0 deletions apps/sim/app/(landing)/changelog/archive/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { CHANGELOG_SECTION, getAllEntryMeta } from '@/lib/changelog'
import { buildCollectionPageJsonLd } from '@/lib/content/seo'
import { buildLandingMetadata } from '@/lib/landing/seo'
import {
ChangelogHeader,
ChangelogLayout,
ChangelogList,
} from '@/app/(landing)/changelog/components'
import { BackLink } from '@/app/(landing)/components/back-link'
import { JsonLd } from '@/app/(landing)/components/json-ld'

export const revalidate = 3600
export const metadata = buildLandingMetadata({
title: 'Changelog Archive | Sim',
description: 'Browse product updates from Sim and find the full technical release history.',
path: '/changelog/archive',
})

export default async function ChangelogArchivePage() {
const entries = await getAllEntryMeta()
return (
<ChangelogLayout>
<JsonLd
data={buildCollectionPageJsonLd(
{ ...CHANGELOG_SECTION, name: 'Changelog Archive', basePath: '/changelog/archive' },
entries
)}
/>
<ChangelogHeader
title='All updates'
actions={<BackLink href='/changelog' label='Back to changelog' />}
/>
<ChangelogList entries={entries} />
<a
href='https://github.com/simstudioai/sim/releases'
target='_blank'
rel='noopener noreferrer'
className='text-[var(--text-secondary)] text-small underline underline-offset-4'
>
Earlier releases and full technical history on GitHub
</a>
</ChangelogLayout>
)
}
81 changes: 36 additions & 45 deletions apps/sim/app/(landing)/changelog/changelog.tsx
Original file line number Diff line number Diff line change
@@ -1,50 +1,41 @@
import { createLogger } from '@sim/logger'
import { getErrorMessage } from '@sim/utils/errors'
import { ChangelogActions, ChangelogTimeline } from '@/app/(landing)/changelog/components'
import type { ChangelogEntry, GitHubRelease } from '@/app/(landing)/changelog/types'
import { mapReleases, releasesEndpoint } from '@/app/(landing)/changelog/utils'
import { ProseHero, ProseShell } from '@/app/(landing)/components/prose-page'

const logger = createLogger('Changelog')

/**
* Changelog page - reuses the shared prose primitives ({@link ProseShell} +
* {@link ProseHero}) so its headline and column match Terms and Privacy, then
* renders the GitHub-release timeline. The first page of releases is
* fetched here on the server at build/revalidate time; the {@link ChangelogTimeline}
* client leaf paginates the rest. Re-authored from the prior dark changelog onto
* the platform light tokens.
*/

const LEAD =
'Every new feature, improvement, and fix in Sim, the open-source AI workspace, with release notes straight from GitHub.'

async function getInitialEntries(): Promise<ChangelogEntry[]> {
try {
// boundary-raw-fetch: external GitHub Releases API (cross-origin), not a same-origin contract
const res = await fetch(releasesEndpoint(1), {
headers: { Accept: 'application/vnd.github+json' },
next: { revalidate: 3600 },
})
const releases = (await res.json()) as GitHubRelease[]
return mapReleases(releases ?? [])
} catch (error) {
logger.warn('Failed to load initial changelog releases from GitHub', {
error: getErrorMessage(error),
})
return []
}
}
import Link from 'next/link'
import { CHANGELOG_SECTION, getAllEntryMeta, LATEST_ENTRY_LIMIT } from '@/lib/changelog'
import { buildCollectionPageJsonLd } from '@/lib/content/seo'
import {
ChangelogActions,
ChangelogHeader,
ChangelogLayout,
ChangelogList,
} from '@/app/(landing)/changelog/components'
import { JsonLd } from '@/app/(landing)/components/json-ld'

export default async function Changelog() {
const entries = await getInitialEntries()

const metadata = await getAllEntryMeta()
const visible = metadata.slice(0, LATEST_ENTRY_LIMIT)
const firstOlderEntry = metadata[LATEST_ENTRY_LIMIT]
return (
<ProseShell>
<ProseHero title='Changelog' lead={LEAD} actions={<ChangelogActions />} />
<section id='releases' aria-label='Release history'>
<ChangelogTimeline initialEntries={entries} />
</section>
</ProseShell>
<ChangelogLayout>
<JsonLd data={buildCollectionPageJsonLd(CHANGELOG_SECTION, visible)} />
<ChangelogHeader title='Changelog' actions={<ChangelogActions />} />
<ChangelogList entries={visible} />
<div className='flex flex-wrap gap-6 text-[var(--text-secondary)] text-sm'>
{firstOlderEntry ? (
<Link
href={`/changelog/archive#${firstOlderEntry.slug}`}
className='underline underline-offset-4'
>
Older updates
</Link>
) : null}
<a
href='https://github.com/simstudioai/sim/releases'
target='_blank'
rel='noopener noreferrer'
className='underline underline-offset-4'
>
Earlier releases on GitHub
</a>
</div>
</ChangelogLayout>
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,20 +9,18 @@ import { GithubOutlineIcon } from '@/components/icons'
* beneath the changelog headline. A small client leaf because `ChipLink` is a
* Client Component and its `leftIcon` is a component reference that cannot cross
* the server→client boundary as a prop (same pattern as the platform pill CTA).
* GitHub is the primary filled chip; Docs and RSS are the default pills.
*/
export function ChangelogActions() {
return (
<div className='flex flex-wrap items-center gap-1'>
<ChipLink
variant='primary'
shape='round'
href='https://github.com/simstudioai/sim/releases'
target='_blank'
rel='noopener noreferrer'
leftIcon={GithubOutlineIcon}
>
View on GitHub
Technical releases
</ChipLink>
<ChipLink
shape='round'
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import { cn } from '@sim/emcn'
import type { ContentPost } from '@/lib/content/schema'
import { BackLink } from '@/app/(landing)/components/back-link'
import { formatPostDate } from '@/app/(landing)/components/content-utils'
import { HOME_TYPE, LANDING_TYPE } from '@/app/(landing)/components/landing-layout'

interface ChangelogArticleProps {
entry: ContentPost
backHref?: string
archiveHref?: string
}

/** A focused reading column contains the update's product recordings and release details. */
export function ChangelogArticle({
entry,
backHref = '/changelog',
archiveHref = '/changelog/archive',
}: ChangelogArticleProps) {
const updated =
entry.updated && entry.updated.slice(0, 10) !== entry.date.slice(0, 10)
? entry.updated
: undefined
return (
<article
id={entry.slug}
className='mx-auto w-full max-w-[900px]'
itemScope
itemType='https://schema.org/BlogPosting'
>
<header className='flex flex-col items-start gap-6'>
<BackLink href={backHref} label='Back to changelog' />
<div className='flex flex-wrap items-center gap-x-4 gap-y-2 text-[var(--text-secondary)] text-sm'>
{entry.draft ? <span>Draft</span> : null}
<time dateTime={entry.date}>{formatPostDate(entry.date)}</time>
{entry.release ? <span>{entry.release.versions.join(' · ')}</span> : null}
{updated ? <time dateTime={entry.updated}>Updated {formatPostDate(updated)}</time> : null}
</div>
<h1
itemProp='headline'
className={cn('text-balance text-[var(--text-primary)]', LANDING_TYPE.proof)}
>
{entry.title}
</h1>
</header>
<section id='update-details' aria-labelledby='update-details-heading' className='mt-8'>
<h2 id='update-details-heading' className='sr-only'>
In this update
</h2>
<entry.Content />
</section>
<footer
className={cn('mt-10 flex flex-wrap gap-6 text-[var(--text-secondary)]', HOME_TYPE.meta)}
>
{entry.release?.url ? (
<a
href={entry.release.url}
target='_blank'
rel='noopener noreferrer'
className='underline underline-offset-4'
>
Technical release notes
</a>
) : null}
<BackLink href={archiveHref} label='All updates' />
</footer>
</article>
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { ChangelogArticle } from './changelog-article'
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import type { ReactNode } from 'react'
import { cn } from '@sim/emcn'
import { LANDING_TYPE } from '@/app/(landing)/components/landing-layout'

interface ChangelogHeaderProps {
title: string
lead?: string
actions?: ReactNode
}

export function ChangelogHeader({ title, lead, actions }: ChangelogHeaderProps) {
return (
<header className='flex flex-col items-start gap-6'>
<h1 className={cn('text-[var(--text-primary)]', LANDING_TYPE.h1)}>{title}</h1>
{lead ? (
<p className={cn('max-w-[640px] text-[var(--text-secondary)]', LANDING_TYPE.lead)}>
{lead}
</p>
) : null}
{actions}
</header>
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { ChangelogHeader } from './changelog-header'
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { cn, Lightbox } from '@sim/emcn'
import Image from 'next/image'
import { LANDING_STAGE_RADIUS } from '@/app/(landing)/components/landing-layout'

interface ChangelogImageProps {
src: string
alt: string
width: number | `${number}`
height: number | `${number}`
caption?: string
}

/** Product screenshots reserve their intrinsic size and use the site's image optimization. */
export function ChangelogImage({ src, alt, width, height, caption }: ChangelogImageProps) {
return (
<figure className='my-6'>
<Lightbox src={src} alt={alt}>
<button
type='button'
aria-label={`Open ${alt} in media viewer`}
className={cn(
'block w-full cursor-zoom-in focus-visible:outline focus-visible:outline-2 focus-visible:outline-[var(--text-primary)] focus-visible:outline-offset-4',
LANDING_STAGE_RADIUS
)}
>
<Image
src={src}
alt={alt}
width={width}
height={height}
sizes='(max-width: 767px) calc(100vw - 40px), 872px'
className={cn('h-auto w-full', LANDING_STAGE_RADIUS)}
/>
</button>
</Lightbox>
{caption ? (
<figcaption className='mt-3 text-[var(--text-secondary)] text-small'>{caption}</figcaption>
) : null}
</figure>
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { ChangelogImage } from './changelog-image'
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import type { ReactNode } from 'react'
import { cn } from '@sim/emcn'
import {
HOME_INSET,
LANDING_CONTENT_WIDTH,
LANDING_GUTTER,
LANDING_HERO_TOP_PADDING,
} from '@/app/(landing)/components/landing-layout'

interface ChangelogLayoutProps {
children: ReactNode
}

/** Matches the marketing page frame; updates inherit the shared navbar and footer. */
export function ChangelogLayout({ children }: ChangelogLayoutProps) {
return (
<main
id='main-content'
className={cn(LANDING_CONTENT_WIDTH, LANDING_GUTTER, LANDING_HERO_TOP_PADDING)}
>
<div className={cn('flex flex-col gap-16 max-sm:gap-12', HOME_INSET)}>{children}</div>
</main>
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { ChangelogLayout } from './changelog-layout'
Loading
Loading