Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
9ee499e
fix(canvas): raw tooltip shows up when hovering block params (#5589)
waleedlatif1 Jul 11, 2026
2ba0b58
fix(rich-markdown-editor): reliable image selection + serialization/p…
waleedlatif1 Jul 11, 2026
3d02bbb
fix(mcp): coerce corrupted consecutiveFailures instead of crashing th…
waleedlatif1 Jul 11, 2026
fca5f10
feat(workflow-editor): open block palette on edge drag-release with a…
TheodoreSpeaks Jul 11, 2026
3a2f4e5
feat(custom-blocks): add deploy_custom_block copilot tool (#5532)
TheodoreSpeaks Jul 11, 2026
d14c304
fix(og-image): match sim.ai OG image colors to live landing tokens (#…
waleedlatif1 Jul 11, 2026
591516a
fix(rich-markdown-editor): fix mention chip losing ambient color insi…
waleedlatif1 Jul 11, 2026
def2d52
fix(docs-og-image): match reference cover template typography exactly…
waleedlatif1 Jul 11, 2026
7c2de1d
feat(providers): add xAI to hosted key rotation pool (#5574)
waleedlatif1 Jul 11, 2026
b486aba
fix(pricing): route Talk to sales CTA to demo request form instead of…
waleedlatif1 Jul 11, 2026
e2e29ee
fix(api): bound request-body reads on speech/knowledge-chunks/help ro…
waleedlatif1 Jul 11, 2026
b731162
fix(file-viewer): sanitize docx hyperlink hrefs to block javascript: …
waleedlatif1 Jul 11, 2026
eb12333
fix(chat-otp): re-check authType before minting deployment auth cooki…
waleedlatif1 Jul 11, 2026
67a2f00
perf(search): cap Cmd-K result groups so typing isn't blocked by resh…
waleedlatif1 Jul 11, 2026
d165712
fix(files-upload): enforce workspace authorization on mothership uplo…
waleedlatif1 Jul 11, 2026
0aa2309
fix(files): unwedge post-stream read-only editor; stop bullet Backspa…
waleedlatif1 Jul 11, 2026
83c532c
improvement(files): show loading spinner in file preview content area…
waleedlatif1 Jul 12, 2026
fcf4e02
fix(landing): repair Lighthouse-flagged CWV audits on production (#5605)
waleedlatif1 Jul 12, 2026
b629292
improvement(chat): shimmer active subagent and tool labels instead of…
waleedlatif1 Jul 12, 2026
1b82b97
fix(custom-blocks): restrict iconUrl to https or internal serve paths…
TheodoreSpeaks Jul 12, 2026
0cc6ed6
improvement(emcn): multi-select selectors + Wizard on ChipModal (#5614)
TheodoreSpeaks Jul 12, 2026
a2f868c
fix(demo): preload the Cal.com booking embed while the visitor fills …
waleedlatif1 Jul 12, 2026
f477faa
fix(rich-markdown-editor): make drag-reorder of an image actually mov…
waleedlatif1 Jul 12, 2026
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
110 changes: 78 additions & 32 deletions apps/docs/app/api/og/route.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,25 +5,28 @@ import type { NextRequest } from 'next/server'
export const runtime = 'edge'

const TITLE_FONT_SIZE = {
large: 105,
medium: 91,
small: 81,
large: 110,
medium: 96,
small: 85,
} as const
/** Average glyph width as a fraction of font size, for this weight/family — used to pack words into lines. */
const AVG_CHAR_WIDTH_EM = 0.46
const LATIN_CHAR_WIDTH_EM = 0.42
/** CJK glyphs (docs ships `ja`/`zh` locales) render near-square, roughly 2.4x a Latin glyph at this weight. */
const CJK_CHAR_WIDTH_EM = 1
const CJK_RANGE = /[\u3000-\u30ff\u3400-\u4dbf\u4e00-\u9fff\uf900-\ufaff\uff00-\uffef]/
const TITLE_BOX_WIDTH = 1020
const FONT_CACHE_REVALIDATE_SECONDS = 60 * 60 * 24 * 30
/** Measured off the reference cover template (`apps/sim/public/library/best-zapier-alternatives/cover.jpg`). */
const INK_COLOR = '#525252'
/** Exact hex from a vector trace of the reference cover template, not an estimate off compressed JPEG pixels. */
const INK_COLOR = '#515151'
const OG_CONTAINER_STYLE = {
height: '100%',
width: '100%',
display: 'flex',
flexDirection: 'column',
justifyContent: 'space-between',
padding: '26px',
background: '#c3c3c3',
fontFamily: 'Season',
background: '#c1c1c1',
fontFamily: 'Soehne',
} satisfies CSSProperties
const OG_HEADER_STYLE = {
display: 'flex',
Expand All @@ -34,10 +37,12 @@ const OG_HEADER_STYLE = {
const OG_TITLE_STYLE = {
display: 'flex',
flexDirection: 'column',
fontWeight: 600,
fontWeight: 500,
color: INK_COLOR,
lineHeight: 1.15,
lineHeight: 1.1,
width: `${TITLE_BOX_WIDTH}px`,
/** Compensates for Satori adding extra invisible leading below the last line instead of splitting it evenly. */
transform: 'translateY(14px)',
} satisfies CSSProperties

function getTitleFontSize(title: string): number {
Expand All @@ -53,6 +58,41 @@ function getTitleStyle(title: string): CSSProperties {
}
}

/** Sums per-character em-widths rather than counting characters, so wide CJK glyphs (docs ships `ja`/`zh`) don't under-wrap. */
function estimateWidthEm(text: string): number {
let width = 0
for (const char of text) {
width += CJK_RANGE.test(char) ? CJK_CHAR_WIDTH_EM : LATIN_CHAR_WIDTH_EM
}
return width
}

/**
* Splits a single word wider than `maxWidthEm` into character-level chunks
* that each fit. CJK titles (docs ships `ja`/`zh` locales) are often
* space-free, so a whole run can arrive as one "word" from `wrapTitleLines`'
* space-based split. Breaking mid-word is correct for CJK, where each glyph
* is independently readable; Latin words never reach this path since they
* stay under `maxWidthEm` in practice.
*/
function splitOversizedWord(word: string, maxWidthEm: number): string[] {
const chunks: string[] = []
let chunk = ''

for (const char of word) {
const candidate = chunk + char
if (estimateWidthEm(candidate) > maxWidthEm && chunk) {
chunks.push(chunk)
chunk = char
} else {
chunk = candidate
}
}
if (chunk) chunks.push(chunk)

return chunks
}

/**
* Greedily packs words into lines that fit `TITLE_BOX_WIDTH` at `fontSize`,
* then joins each line with U+00A0 instead of a plain space. Satori
Expand All @@ -63,14 +103,25 @@ function getTitleStyle(title: string): CSSProperties {
* (which is also disabled here — lines are pre-split, not auto-wrapped).
*/
function wrapTitleLines(title: string, fontSize: number): string[] {
const maxCharsPerLine = Math.floor(TITLE_BOX_WIDTH / (fontSize * AVG_CHAR_WIDTH_EM))
const maxWidthEm = TITLE_BOX_WIDTH / fontSize
const words = title.split(' ')
const lines: string[] = []
let current = ''

for (const word of words) {
if (estimateWidthEm(word) > maxWidthEm) {
if (current) {
lines.push(current)
current = ''
}
const chunks = splitOversizedWord(word, maxWidthEm)
lines.push(...chunks.slice(0, -1))
current = chunks[chunks.length - 1] ?? ''
continue
}

const candidate = current ? `${current} ${word}` : word
if (candidate.length > maxCharsPerLine && current) {
if (estimateWidthEm(candidate) > maxWidthEm && current) {
lines.push(current)
current = word
} else {
Expand All @@ -83,21 +134,16 @@ function wrapTitleLines(title: string, fontSize: number): string[] {
}

/**
* Loads a static (600/semibold) TTF instance of the site's own Season Sans
* font — the platform's real brand/body font, also used by the library/blog
* cover template this OG image matches. Instantiated from the variable font
* at `apps/docs/app/fonts/SeasonSansUprightsVF.woff2` (`fonttools
* varLib.instancer wght=600`, then flavor-stripped to plain TTF) rather than
* loading the variable WOFF2 directly: Satori (`next/og`'s renderer) can't
* parse variable fonts without excessive memory use, and can't parse WOFF2
* at all ("Unsupported OpenType signature wOF2") — it needs an uncompressed
* TTF/OTF. Fetched over HTTP since the edge runtime has no filesystem access
* — served from `/static/fonts/` (not `/fonts/`) so it isn't intercepted by
* the site's i18n proxy (`proxy.ts`), whose matcher excludes `static` but
* not `fonts`.
* Loads Söhne Kräftig (weight 500), the typeface used on the reference cover
* template this OG image matches. Converted to a plain TTF from the
* last-shipped `soehne-kraftig.woff2` since Satori (`next/og`'s renderer)
* can't parse WOFF2 or variable fonts. Fetched over HTTP since the edge
* runtime has no filesystem access — served from `/static/fonts/` (not
* `/fonts/`) so it isn't intercepted by the site's i18n proxy (`proxy.ts`),
* whose matcher excludes `static` but not `fonts`.
*/
async function loadSeasonFont(baseUrl: string): Promise<ArrayBuffer> {
const response = await fetch(new URL('/static/fonts/SeasonSans-600-static.ttf', baseUrl), {
async function loadTitleFont(baseUrl: string): Promise<ArrayBuffer> {
const response = await fetch(new URL('/static/fonts/Soehne-Kraftig.ttf', baseUrl), {
next: { revalidate: FONT_CACHE_REVALIDATE_SECONDS },
})

Expand Down Expand Up @@ -128,16 +174,16 @@ function SimWordmark() {
)
}

/** Diagonal "open" arrow, top-right — matches the library/blog cover template. */
/** Diagonal "open" arrow, top-right — square caps and a miter join to match the reference's sharp corners. */
function CornerArrow() {
return (
<svg width='58' height='58' viewBox='0 0 24 24' fill='none'>
<path
d='M2 22 22 2M22 2H12M22 2V12'
stroke={INK_COLOR}
strokeWidth={3.6}
strokeLinecap='round'
strokeLinejoin='round'
strokeLinecap='square'
strokeLinejoin='miter'
/>
</svg>
)
Expand All @@ -153,7 +199,7 @@ export async function GET(request: NextRequest) {
const { searchParams } = new URL(request.url)
const title = searchParams.get('title') || 'Documentation'

const fontData = await loadSeasonFont(request.url)
const fontData = await loadTitleFont(request.url)
const fontSize = getTitleFontSize(title)
const titleLines = wrapTitleLines(title, fontSize)

Expand All @@ -175,10 +221,10 @@ export async function GET(request: NextRequest) {
height: 675,
fonts: [
{
name: 'Season',
name: 'Soehne',
data: fontData,
style: 'normal',
weight: 600,
weight: 500,
},
],
}
Expand Down
10 changes: 5 additions & 5 deletions apps/docs/app/llms.txt/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ Sim is the open-source AI workspace where teams build, deploy, and manage AI age

## Documentation Overview

This file provides an overview of our documentation. For full content of all pages, see ${baseUrl}/llms-full.txt
This file provides an overview of our documentation. For full content of all pages, see [llms-full.txt](${baseUrl}/llms-full.txt).

## Main Sections

Expand All @@ -54,16 +54,16 @@ ${Object.entries(sections)
.split('-')
.map((word) => word.charAt(0).toUpperCase() + word.slice(1))
.join(' ')
return `### ${sectionTitle}\n\n${items.map((item) => `- ${item.title}: ${item.url}${item.description ? `\n ${item.description}` : ''}`).join('\n')}`
return `### ${sectionTitle}\n\n${items.map((item) => `- [${item.title}](${item.url})${item.description ? `\n ${item.description}` : ''}`).join('\n')}`
})
.join('\n\n')}

## Additional Resources

- Full documentation content: ${baseUrl}/llms-full.txt
- [Full documentation content](${baseUrl}/llms-full.txt)
- Individual page content: ${baseUrl}/llms.mdx/[page-path]
- API documentation: ${baseUrl}/api-reference/
- Tool integrations: ${baseUrl}/tools/
- [API documentation](${baseUrl}/api-reference/)
- [Tool integrations](${baseUrl}/tools/)

## Statistics

Expand Down
51 changes: 0 additions & 51 deletions apps/docs/public/llms.txt

This file was deleted.

Binary file not shown.
Binary file added apps/docs/public/static/fonts/Soehne-Kraftig.ttf
Binary file not shown.
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@ export function ContentIndexPage({
sizes='(max-width: 768px) 100vw, (max-width: 1024px) 50vw, 33vw'
className='object-cover'
priority={index < 3}
fetchPriority={index === 0 ? 'high' : undefined}
unoptimized
/>
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ export function ContentPostPage({
className='h-auto w-full'
sizes='(max-width: 768px) 100vw, 450px'
priority
fetchPriority='high'
itemProp='image'
unoptimized
/>
Expand Down
5 changes: 3 additions & 2 deletions apps/sim/app/(landing)/components/cta/cta.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { ChipLink } from '@sim/emcn'
import { DEMO_HREF, SIGNUP_HREF } from '@/app/(landing)/constants'

/**
* Landing pre-footer CTA - the page's final conversion band. A tall, centered
Expand Down Expand Up @@ -28,10 +29,10 @@ export function Cta() {
Build your first agent today.
</h2>
<div className='flex items-center gap-1'>
<ChipLink variant='primary' href='/signup'>
<ChipLink variant='primary' href={SIGNUP_HREF}>
Get started
</ChipLink>
<ChipLink href='/demo' className='border border-[var(--border-1)]'>
<ChipLink href={DEMO_HREF} className='border border-[var(--border-1)]'>
Contact sales
</ChipLink>
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,18 @@ import { CalloutFrame } from '@/app/(landing)/components/features/components/cal
* the right AND bottom edges bleed past the media stage's clip - a zoomed-in
* peek at part of the product rather than a complete miniature, scaling
* proportionally with the aspect-locked stage. Decorative.
*
* `sizes` is derived directly from the section's grid math rather than
* approximated, then rounded up to the worst-case (peak render/viewport
* ratio) in each tier so the browser never under-fetches:
* `callout = 1.25 * (viewport - 2*gutter - 32px card padding - [40px gap +
* 386px fixed copy column, desktop only])`, gutter = `px-20`/`max-lg:px-8`/
* `max-sm:px-5` from `Features`'s grid, matching `FeatureCard`'s
* `max-lg:grid-cols-1` stack. Peak ratios (verified against a static
* reproduction of this exact layout rendered at each Tailwind breakpoint):
* ~113.3% at the `max-width: 1023px` stacked tier's own upper edge, ~108.6%
* at `1460px` (the container's cap, where render width stops growing with
* viewport - hence the final tier is a flat px value, not a vw fraction).
*/
export function IntegrationsCallout() {
return (
Expand All @@ -29,7 +41,7 @@ export function IntegrationsCallout() {
src='/landing/feature-integrate-ui.png'
alt=''
fill
sizes='1050px'
sizes='(max-width: 1023px) 114vw, (max-width: 1460px) 109vw, 1053px'
className='object-cover'
/>
</CalloutFrame>
Expand Down
5 changes: 3 additions & 2 deletions apps/sim/app/(landing)/components/hero-cta/hero-cta.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { ChipLink, cn } from '@sim/emcn'
import { DEMO_HREF, SIGNUP_HREF } from '@/app/(landing)/constants'

/**
* Hero-scale sizing shared by both CTAs - one step up from the navbar's 30px
Expand All @@ -24,11 +25,11 @@ const CTA_SIZE = 'h-[36px] px-[0.571em] text-[15px] [&>span]:[font-size:inherit]
export function HeroCta() {
return (
<div className='flex items-center gap-2 max-sm:w-full max-sm:flex-col max-sm:items-stretch'>
<ChipLink variant='primary' href='/demo' className={CTA_SIZE}>
<ChipLink variant='primary' href={DEMO_HREF} className={CTA_SIZE}>
Request a demo
</ChipLink>
<ChipLink
href='/signup'
href={SIGNUP_HREF}
prefetch={false}
className={cn(CTA_SIZE, 'border border-[var(--border-1)] max-sm:justify-center')}
>
Expand Down
1 change: 1 addition & 0 deletions apps/sim/app/(landing)/components/hero/hero.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,7 @@ export function Hero() {
alt=''
fill
priority
fetchPriority='high'
quality={90}
sizes='(max-width: 1460px) 100vw, 1300px'
className='object-cover'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
NAVBAR_GLASS_SURFACE,
useNavbarFrost,
} from '@/app/(landing)/components/navbar/components/navbar-shell'
import { DEMO_HREF, SIGNUP_HREF } from '@/app/(landing)/constants'

/**
* Mobile navigation - the `< lg` counterpart to the desktop nav clusters.
Expand Down Expand Up @@ -70,7 +71,7 @@ export function MobileNav({ stars }: MobileNavProps) {

return (
<div className='ml-auto flex items-center gap-2 lg:hidden'>
<ChipLink variant='primary' href='/signup' prefetch={false}>
<ChipLink variant='primary' href={SIGNUP_HREF} prefetch={false}>
Sign up
</ChipLink>
<button
Expand Down Expand Up @@ -166,7 +167,7 @@ export function MobileNav({ stars }: MobileNavProps) {
</ChipLink>
<ChipLink
variant='primary'
href='/demo'
href={DEMO_HREF}
fullWidth
flush
className='h-[40px] justify-center [&>span]:flex-none'
Expand Down
Loading
Loading