Skip to content

Commit 417aed1

Browse files
Bill Leoutsakoscursoragent
authored andcommitted
Merge staging into e2e/settings-playwright
Reconcile the combined Bun lockfile so the umbrella PR's merge commit installs reproducibly under the frozen CI gate. Co-authored-by: Cursor <cursoragent@cursor.com>
2 parents 41c1f24 + 79c57bf commit 417aed1

130 files changed

Lines changed: 5809 additions & 1854 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.

.claude/rules/landing-seo-geo.md

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
---
22
paths:
33
- "apps/sim/app/(landing)/**/*.tsx"
4+
- "apps/sim/content/**/*.mdx"
45
---
56

67
# Landing Page — SEO / GEO
@@ -24,3 +25,22 @@ paths:
2425
- **Keyword density**: first 150 visible chars of Hero must name "Sim", "AI workspace", "AI agents".
2526
- **sr-only summaries**: Hero and Templates each have a `<p className="sr-only">` (~50 words) as an atomic product/catalog summary for AI citation.
2627
- **Specific numbers**: prefer concrete figures ("1,000+ integrations", "15+ AI providers") over vague claims.
28+
29+
## Citations and linking (`/library`, `/blog`, `/comparisons`)
30+
31+
The Princeton GEO study (Aggarwal et al., KDD 2024, [arXiv:2311.09735](https://arxiv.org/abs/2311.09735)) found that adding citations, quotations, and statistics were the three strongest of nine tested tactics, worth 30–40% relative lifts in AI-answer visibility. Sourcing is also what makes a claim checkable by a human reader.
32+
33+
- **Every third-party factual claim carries an outbound source link.** Pricing, rate limits, feature availability, licensing, compliance certifications — link the primary source (the vendor's own pricing page, docs, changelog, or license file), not a secondary blog. External links get `rel="noopener noreferrer"`.
34+
- **Prefer the primary source over a roundup.** Citing another vendor's comparison post to substantiate a fact about them is second-hand and ages badly.
35+
- **Internal links: 3–5 per library post**, pointing at genuinely related library entries, using real `href`s (Next `<Link>` in TSX; a plain markdown link in MDX). A post with zero internal links is a dead end for crawlers and readers alike.
36+
- **Never fabricate a citation.** An unlinked claim is better than a link that does not substantiate it. If a number cannot be sourced, cut the number.
37+
38+
## Freshness
39+
40+
Answer engines weight recency to avoid repeating stale facts, and a reader deciding whether to trust a pricing comparison wants to know when it was last checked. The vendor-published "fresh content earns Nx more citations" figures are directional, not measured — the reason to do this is that both signals must agree and both must be real.
41+
42+
- **Emit `dateModified`** in the page's structured data (JSON-LD or microdata), and emit it exactly once per document.
43+
- **Show the same date to the reader.** `/comparisons/[provider]` renders "Last verified …" from `getLatestVerifiedDate()`; `/library` and `/blog` posts render "Updated …" next to the publish date. A date that exists only in metadata is invisible to a reader deciding whether to trust the page.
44+
- **Only surface a modified date when it differs from the publish date** — an "Updated" label on the publish day is noise.
45+
- **Bump the date only on a substantive edit.** Touching frontmatter without changing the content is date-washing; it degrades the signal for every other page on the domain.
46+
- **Comparison facts are dated at the fact level.** Every `Fact` in `apps/sim/lib/compare/data` carries `sources: [{ url, label, asOf }]`. Re-checking a fact means updating its `asOf`, which flows through `getLatestVerifiedDate()` to the visible date, the JSON-LD, and the sitemap.

apps/docs/components/workflow-preview/block-display-workflows.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -326,11 +326,11 @@ export const BLOCK_DISPLAY_WORKFLOWS: Record<string, PreviewWorkflow> = {
326326
},
327327
webhook_trigger: {
328328
id: 'webhook_trigger',
329-
name: 'Webhook',
329+
name: 'Webhook Trigger',
330330
blocks: [
331331
{
332332
id: 'webhook_trigger',
333-
name: 'Webhook',
333+
name: 'Webhook Trigger',
334334
type: 'webhook',
335335
bgColor: '#10B981',
336336
position: { x: 0, y: 0 },

apps/docs/content/docs/en/workflows/blocks/webhook.mdx

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
11
---
2-
title: Outgoing Webhook
3-
description: The Outgoing Webhook block sends an HTTP POST to an external endpoint, with automatic headers and optional signing.
2+
title: Webhook
3+
description: The Webhook block sends an HTTP POST to an external endpoint, with automatic headers and optional signing.
44
pageType: reference
55
---
66

77
import { Callout } from 'fumadocs-ui/components/callout'
88
import { BlockPreview, WorkflowPreview, WEBHOOK_NOTIFY_WORKFLOW, WEBHOOK_TRIGGER_WORKFLOW } from '@/components/workflow-preview'
99
import { FAQ } from '@/components/ui/faq'
1010

11-
The Outgoing Webhook block sends HTTP POST requests to external webhook endpoints with automatic webhook headers and optional HMAC signing.
11+
The Webhook block sends HTTP POST requests to external webhook endpoints with automatic webhook headers and optional HMAC signing.
1212

1313
<BlockPreview type="webhook" />
1414

@@ -77,16 +77,16 @@ Format the result, then POST it to a Slack, Discord, or custom endpoint.
7777

7878
<WorkflowPreview workflow={WEBHOOK_TRIGGER_WORKFLOW} />
7979

80-
When the Condition passes, the Outgoing Webhook starts a process in another system.
80+
When the Condition passes, the Webhook starts a process in another system.
8181

8282
<Callout>
83-
The Outgoing Webhook block always uses POST. For other HTTP methods or more control, use the [API block](/workflows/blocks/api).
83+
The Webhook block always uses POST. For other HTTP methods or more control, use the [API block](/workflows/blocks/api).
8484
</Callout>
8585

8686
<FAQ items={[
87-
{ question: "Can I use HTTP methods other than POST?", answer: "No. The Outgoing Webhook block always sends POST requests. If you need GET, PUT, DELETE, or PATCH, use the API block instead, which supports all standard HTTP methods." },
87+
{ question: "Can I use HTTP methods other than POST?", answer: "No. The Webhook block always sends POST requests. If you need GET, PUT, DELETE, or PATCH, use the API block instead, which supports all standard HTTP methods." },
8888
{ question: "How does HMAC payload signing work?", answer: "When you provide a signing secret, the block generates an HMAC-SHA256 signature of the payload and includes it in the X-Webhook-Signature header in the format t=timestamp,v1=signature. The receiver can verify by computing HMAC-SHA256(secret, \"timestamp.body\") and comparing with the v1 value." },
8989
{ question: "What headers are added automatically?", answer: "Every webhook request automatically includes Content-Type (application/json), X-Webhook-Timestamp (Unix timestamp in milliseconds), X-Delivery-ID (unique UUID), and Idempotency-Key (same as X-Delivery-ID for deduplication)." },
9090
{ question: "Can custom headers override the automatic ones?", answer: "Yes. Any custom headers you define in the Additional Headers section will override automatic headers that share the same name." },
91-
{ question: "How is the Outgoing Webhook block different from the API block?", answer: "The Outgoing Webhook block is purpose-built for webhook delivery: it is POST-only, automatically adds webhook-specific headers (timestamp, delivery ID, idempotency key), and supports optional HMAC signing. The API block is more general-purpose with support for all HTTP methods, query parameters, and configurable retries." },
91+
{ question: "How is the Webhook block different from the API block?", answer: "The Webhook block is purpose-built for webhook delivery: it is POST-only, automatically adds webhook-specific headers (timestamp, delivery ID, idempotency key), and supports optional HMAC signing. The API block is more general-purpose with support for all HTTP methods, query parameters, and configurable retries." },
9292
]} />

apps/docs/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@
3131
"fumadocs-openapi": "10.8.1",
3232
"fumadocs-ui": "16.8.5",
3333
"lucide-react": "^0.511.0",
34-
"next": "16.2.6",
34+
"next": "16.2.11",
3535
"next-themes": "^0.4.6",
3636
"postgres": "^3.4.5",
3737
"react": "19.2.4",

apps/sim/app/(landing)/comparisons/[provider]/page.tsx

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -177,7 +177,16 @@ export default async function ComparisonProviderPage({
177177
Sim is the open-source AI workspace where teams build, deploy, and manage AI agents
178178
visually, conversationally, or with code. Here is how Sim compares to{' '}
179179
{competitor.name} on platform architecture, AI capabilities, integrations, pricing,
180-
security, and support. Every fact below is sourced and dated.
180+
security, and support. Every fact below is sourced and dated, last verified{' '}
181+
<time dateTime={latestVerified.toISOString().slice(0, 10)}>
182+
{latestVerified.toLocaleDateString('en-US', {
183+
month: 'long',
184+
day: 'numeric',
185+
year: 'numeric',
186+
timeZone: 'UTC',
187+
})}
188+
</time>
189+
.
181190
</p>
182191
<p className='sr-only'>
183192
Sim is an open-source AI workspace for building, deploying, and managing AI agents.

apps/sim/app/(landing)/components/content-post-page/content-post-page.tsx

Lines changed: 39 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,16 @@ import { BackLink } from '@/app/(landing)/components/back-link'
77
import { JsonLd } from '@/app/(landing)/components/json-ld'
88
import { ShareButton } from '@/app/(landing)/components/share-button'
99

10+
/** Renders an ISO date as "Jul 1, 2026". Pinned to UTC so the day matches the frontmatter date in every reader's timezone. */
11+
function formatDate(iso: string): string {
12+
return new Date(iso).toLocaleDateString('en-US', {
13+
month: 'short',
14+
day: 'numeric',
15+
year: 'numeric',
16+
timeZone: 'UTC',
17+
})
18+
}
19+
1020
interface ContentPostPageProps {
1121
/** Route base path, e.g. `/blog` or `/library`. */
1222
basePath: string
@@ -32,6 +42,8 @@ export function ContentPostPage({
3242
shareUrl,
3343
}: ContentPostPageProps) {
3444
const Article = post.Content
45+
const modifiedIso = post.updated ?? post.date
46+
const showUpdated = modifiedIso.slice(0, 10) !== post.date.slice(0, 10)
3547

3648
return (
3749
<article className='w-full bg-[var(--bg)]' itemScope itemType='https://schema.org/BlogPosting'>
@@ -73,20 +85,32 @@ export function ContentPostPage({
7385
{post.description}
7486
</p>
7587
</div>
76-
<div className='mt-6 flex items-center gap-6'>
77-
<time
78-
className='text-[var(--text-muted)] text-xs uppercase tracking-[0.1em]'
79-
dateTime={post.date}
80-
itemProp='datePublished'
81-
>
82-
{new Date(post.date).toLocaleDateString('en-US', {
83-
month: 'short',
84-
day: 'numeric',
85-
year: 'numeric',
86-
timeZone: 'UTC',
87-
})}
88-
</time>
89-
<meta itemProp='dateModified' content={post.updated ?? post.date} />
88+
<div className='mt-6 flex flex-wrap items-center gap-x-6 gap-y-2'>
89+
<div className='flex items-center gap-2'>
90+
<time
91+
className='text-[var(--text-muted)] text-xs uppercase tracking-[0.1em]'
92+
dateTime={post.date}
93+
itemProp='datePublished'
94+
>
95+
{formatDate(post.date)}
96+
</time>
97+
{showUpdated ? (
98+
<>
99+
<span aria-hidden='true' className='text-[var(--text-muted)] text-xs'>
100+
·
101+
</span>
102+
<time
103+
className='text-[var(--text-muted)] text-xs uppercase tracking-[0.1em]'
104+
dateTime={modifiedIso}
105+
itemProp='dateModified'
106+
>
107+
Updated {formatDate(modifiedIso)}
108+
</time>
109+
</>
110+
) : (
111+
<meta itemProp='dateModified' content={modifiedIso} />
112+
)}
113+
</div>
90114
<div className='flex items-center gap-3'>
91115
{(post.authors || [post.author]).map((a) => (
92116
<div key={a?.name} className='flex items-center gap-2'>
@@ -151,12 +175,7 @@ export function ContentPostPage({
151175
</div>
152176
<div className='flex flex-col gap-2'>
153177
<span className='text-[var(--text-muted)] text-xs uppercase tracking-[0.1em]'>
154-
{new Date(p.date).toLocaleDateString('en-US', {
155-
month: 'short',
156-
day: 'numeric',
157-
year: 'numeric',
158-
timeZone: 'UTC',
159-
})}
178+
{formatDate(p.date)}
160179
</span>
161180
<h3 className='text-[var(--text-primary)] text-lg leading-tight tracking-[-0.01em]'>
162181
{p.title}

apps/sim/app/(landing)/components/hero/components/hero-header/hero-header.tsx

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,12 @@ import { LANDING_HERO_CTA_GAP } from '@/app/(landing)/components/landing-layout'
66

77
interface LandingHeroHeaderProps {
88
description: string
9+
/**
10+
* Optional second paragraph beneath the description - a self-contained
11+
* definition of the page's subject, kept quotable for answer engines (GEO).
12+
* Omitted by the homepage, so its hero renders unchanged.
13+
*/
14+
definition?: string
915
eyebrow?: ReactNode
1016
heading: ReactNode
1117
headingId: string
@@ -17,6 +23,7 @@ interface LandingHeroHeaderProps {
1723
*/
1824
export function LandingHeroHeader({
1925
description,
26+
definition,
2027
eyebrow,
2128
heading,
2229
headingId,
@@ -37,6 +44,12 @@ export function LandingHeroHeader({
3744
{description}
3845
</p>
3946

47+
{definition ? (
48+
<p className='w-full min-w-0 max-w-[58ch] text-pretty text-[var(--text-muted)] text-base leading-[1.5]'>
49+
{definition}
50+
</p>
51+
) : null}
52+
4053
<div className={cn('max-sm:w-full', LANDING_HERO_CTA_GAP)}>
4154
<HeroCta />
4255
</div>

apps/sim/app/(landing)/components/hero/hero.tsx

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -86,11 +86,12 @@ export function Hero() {
8686
headingId='hero-heading'
8787
heading={
8888
<>
89-
Sim is the AI workspace <br />
90-
for building AI agents.
89+
Sim is the AI workspace for <br />
90+
building and managing AI agents.
9191
</>
9292
}
93-
description='Open source, with 1,000+ integrations and every major LLM. Build, deploy, and manage agents visually, conversationally, or with code.'
93+
description='Sim is an AI agent and workflow builder for teams creating agents that automate real work. Design workflows visually, describe what you need in natural language, or use code for complete control.'
94+
definition='Connect your agents to 1,000+ integrations and every major LLM, then deploy, monitor, and improve them from one collaborative, open-source workspace.'
9495
/>
9596

9697
<div

apps/sim/app/(landing)/components/home-structured-data/home-structured-data.tsx

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,14 +24,21 @@ import { JsonLd } from '@/app/(landing)/components/json-ld'
2424
export const HOME_PAGE_DESCRIPTION =
2525
'Sim is the open-source AI workspace where teams build, deploy, and manage AI agents across 1,000+ integrations and every major LLM, visually or with code.'
2626

27+
/**
28+
* The home page's canonical title - the single string shared by the
29+
* `<title>`, OG/Twitter titles (`page.tsx`), and the JSON-LD `WebPage.name`
30+
* below, so the title surfaces never drift.
31+
*/
32+
export const HOME_PAGE_TITLE = 'The AI Workspace | Build, Deploy & Manage AI Agents | Sim'
33+
2734
const HOME_JSON_LD = {
2835
'@context': 'https://schema.org',
2936
'@graph': [
3037
{
3138
'@type': 'WebPage',
3239
'@id': `${SITE_URL}#webpage`,
3340
url: SITE_URL,
34-
name: 'Sim, The AI Workspace | Build, Deploy & Manage AI Agents',
41+
name: HOME_PAGE_TITLE,
3542
isPartOf: { '@id': `${SITE_URL}#website` },
3643
about: { '@id': `${SITE_URL}#software` },
3744
datePublished: '2024-01-01T00:00:00+00:00',
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
export { HOME_PAGE_DESCRIPTION, HomeStructuredData } from './home-structured-data'
1+
export { HOME_PAGE_DESCRIPTION, HOME_PAGE_TITLE, HomeStructuredData } from './home-structured-data'

0 commit comments

Comments
 (0)