Skip to content

Commit eb07a08

Browse files
v0.5.35: helm updates, copilot improvements, 404 for docs, salesforce fixes, subflow resize clamping
2 parents 67cfb21 + 2a7f51a commit eb07a08

File tree

94 files changed

+2096
-1260
lines changed

Some content is hidden

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

94 files changed

+2096
-1260
lines changed

.github/workflows/test-build.yml

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,19 @@ jobs:
4848
ENCRYPTION_KEY: '7cf672e460e430c1fba707575c2b0e2ad5a99dddf9b7b7e3b5646e630861db1c' # dummy key for CI only
4949
run: bun run test
5050

51+
- name: Check schema and migrations are in sync
52+
working-directory: packages/db
53+
run: |
54+
bunx drizzle-kit generate --config=./drizzle.config.ts
55+
if [ -n "$(git status --porcelain ./migrations)" ]; then
56+
echo "❌ Schema and migrations are out of sync!"
57+
echo "Run 'cd packages/db && bunx drizzle-kit generate' and commit the new migrations."
58+
git status --porcelain ./migrations
59+
git diff ./migrations
60+
exit 1
61+
fi
62+
echo "✅ Schema and migrations are in sync"
63+
5164
- name: Build application
5265
env:
5366
NODE_OPTIONS: '--no-warnings'

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -188,7 +188,7 @@ DATABASE_URL="postgresql://postgres:your_password@localhost:5432/simstudio"
188188

189189
Then run the migrations:
190190
```bash
191-
cd apps/sim # Required so drizzle picks correct .env file
191+
cd packages/db # Required so drizzle picks correct .env file
192192
bunx drizzle-kit migrate --config=./drizzle.config.ts
193193
```
194194

apps/docs/app/[lang]/not-found.tsx

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import { DocsBody, DocsPage } from 'fumadocs-ui/page'
2+
3+
export const metadata = {
4+
title: 'Page Not Found',
5+
}
6+
7+
export default function NotFound() {
8+
return (
9+
<DocsPage>
10+
<DocsBody>
11+
<div className='flex min-h-[60vh] flex-col items-center justify-center text-center'>
12+
<h1 className='mb-4 bg-gradient-to-b from-[#8357FF] to-[#6F3DFA] bg-clip-text font-bold text-8xl text-transparent'>
13+
404
14+
</h1>
15+
<h2 className='mb-2 font-semibold text-2xl text-foreground'>Page Not Found</h2>
16+
<p className='text-muted-foreground'>
17+
The page you're looking for doesn't exist or has been moved.
18+
</p>
19+
</div>
20+
</DocsBody>
21+
</DocsPage>
22+
)
23+
}

apps/sim/app/(auth)/login/login-form.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -573,10 +573,10 @@ export default function LoginPage({
573573
<Dialog open={forgotPasswordOpen} onOpenChange={setForgotPasswordOpen}>
574574
<DialogContent className='auth-card auth-card-shadow max-w-[540px] rounded-[10px] border backdrop-blur-sm'>
575575
<DialogHeader>
576-
<DialogTitle className='auth-text-primary font-semibold text-xl tracking-tight'>
576+
<DialogTitle className='font-semibold text-black text-xl tracking-tight'>
577577
Reset Password
578578
</DialogTitle>
579-
<DialogDescription className='auth-text-secondary text-sm'>
579+
<DialogDescription className='text-muted-foreground text-sm'>
580580
Enter your email address and we'll send you a link to reset your password if your
581581
account exists.
582582
</DialogDescription>

apps/sim/app/(landing)/studio/page.tsx

Lines changed: 2 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
1-
import Image from 'next/image'
21
import Link from 'next/link'
3-
import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar'
42
import { getAllPostMeta } from '@/lib/blog/registry'
53
import { soehne } from '@/app/_styles/fonts/soehne/soehne'
4+
import { PostGrid } from '@/app/(landing)/studio/post-grid'
65

76
export const revalidate = 3600
87

@@ -18,7 +17,6 @@ export default async function StudioIndex({
1817
const all = await getAllPostMeta()
1918
const filtered = tag ? all.filter((p) => p.tags.includes(tag)) : all
2019

21-
// Sort to ensure featured post is first on page 1
2220
const sorted =
2321
pageNum === 1
2422
? filtered.sort((a, b) => {
@@ -63,69 +61,7 @@ export default async function StudioIndex({
6361
</div> */}
6462

6563
{/* Grid layout for consistent rows */}
66-
<div className='grid grid-cols-1 gap-4 md:grid-cols-2 md:gap-6 lg:grid-cols-3'>
67-
{posts.map((p, i) => {
68-
return (
69-
<Link key={p.slug} href={`/studio/${p.slug}`} className='group flex flex-col'>
70-
<div className='flex h-full flex-col overflow-hidden rounded-xl border border-gray-200 transition-colors duration-300 hover:border-gray-300'>
71-
<Image
72-
src={p.ogImage}
73-
alt={p.title}
74-
width={800}
75-
height={450}
76-
className='h-48 w-full object-cover'
77-
sizes='(max-width: 768px) 100vw, (max-width: 1024px) 50vw, 33vw'
78-
loading='lazy'
79-
unoptimized
80-
/>
81-
<div className='flex flex-1 flex-col p-4'>
82-
<div className='mb-2 text-gray-600 text-xs'>
83-
{new Date(p.date).toLocaleDateString('en-US', {
84-
month: 'short',
85-
day: 'numeric',
86-
year: 'numeric',
87-
})}
88-
</div>
89-
<h3 className='shine-text mb-1 font-medium text-lg leading-tight'>{p.title}</h3>
90-
<p className='mb-3 line-clamp-3 flex-1 text-gray-700 text-sm'>{p.description}</p>
91-
<div className='flex items-center gap-2'>
92-
<div className='-space-x-1.5 flex'>
93-
{(p.authors && p.authors.length > 0 ? p.authors : [p.author])
94-
.slice(0, 3)
95-
.map((author, idx) => (
96-
<Avatar key={idx} className='size-4 border border-white'>
97-
<AvatarImage src={author?.avatarUrl} alt={author?.name} />
98-
<AvatarFallback className='border border-white bg-gray-100 text-[10px] text-gray-600'>
99-
{author?.name.slice(0, 2)}
100-
</AvatarFallback>
101-
</Avatar>
102-
))}
103-
</div>
104-
<span className='text-gray-600 text-xs'>
105-
{(p.authors && p.authors.length > 0 ? p.authors : [p.author])
106-
.slice(0, 2)
107-
.map((a) => a?.name)
108-
.join(', ')}
109-
{(p.authors && p.authors.length > 0 ? p.authors : [p.author]).length > 2 && (
110-
<>
111-
{' '}
112-
and{' '}
113-
{(p.authors && p.authors.length > 0 ? p.authors : [p.author]).length - 2}{' '}
114-
other
115-
{(p.authors && p.authors.length > 0 ? p.authors : [p.author]).length - 2 >
116-
1
117-
? 's'
118-
: ''}
119-
</>
120-
)}
121-
</span>
122-
</div>
123-
</div>
124-
</div>
125-
</Link>
126-
)
127-
})}
128-
</div>
64+
<PostGrid posts={posts} />
12965

13066
{totalPages > 1 && (
13167
<div className='mt-10 flex items-center justify-center gap-3'>
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
'use client'
2+
3+
import Image from 'next/image'
4+
import Link from 'next/link'
5+
import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar'
6+
7+
interface Author {
8+
id: string
9+
name: string
10+
avatarUrl?: string
11+
url?: string
12+
}
13+
14+
interface Post {
15+
slug: string
16+
title: string
17+
description: string
18+
date: string
19+
ogImage: string
20+
author: Author
21+
authors?: Author[]
22+
featured?: boolean
23+
}
24+
25+
export function PostGrid({ posts }: { posts: Post[] }) {
26+
return (
27+
<div className='grid grid-cols-1 gap-4 md:grid-cols-2 md:gap-6 lg:grid-cols-3'>
28+
{posts.map((p, index) => (
29+
<Link key={p.slug} href={`/studio/${p.slug}`} className='group flex flex-col'>
30+
<div className='flex h-full flex-col overflow-hidden rounded-xl border border-gray-200 transition-colors duration-300 hover:border-gray-300'>
31+
{/* Image container with fixed aspect ratio to prevent layout shift */}
32+
<div className='relative aspect-video w-full overflow-hidden'>
33+
<Image
34+
src={p.ogImage}
35+
alt={p.title}
36+
sizes='(max-width: 768px) 100vw, (max-width: 1024px) 50vw, 33vw'
37+
unoptimized
38+
priority={index < 6}
39+
loading={index < 6 ? undefined : 'lazy'}
40+
fill
41+
style={{ objectFit: 'cover' }}
42+
/>
43+
</div>
44+
<div className='flex flex-1 flex-col p-4'>
45+
<div className='mb-2 text-gray-600 text-xs'>
46+
{new Date(p.date).toLocaleDateString('en-US', {
47+
month: 'short',
48+
day: 'numeric',
49+
year: 'numeric',
50+
})}
51+
</div>
52+
<h3 className='shine-text mb-1 font-medium text-lg leading-tight'>{p.title}</h3>
53+
<p className='mb-3 line-clamp-3 flex-1 text-gray-700 text-sm'>{p.description}</p>
54+
<div className='flex items-center gap-2'>
55+
<div className='-space-x-1.5 flex'>
56+
{(p.authors && p.authors.length > 0 ? p.authors : [p.author])
57+
.slice(0, 3)
58+
.map((author, idx) => (
59+
<Avatar key={idx} className='size-4 border border-white'>
60+
<AvatarImage src={author?.avatarUrl} alt={author?.name} />
61+
<AvatarFallback className='border border-white bg-gray-100 text-[10px] text-gray-600'>
62+
{author?.name.slice(0, 2)}
63+
</AvatarFallback>
64+
</Avatar>
65+
))}
66+
</div>
67+
<span className='text-gray-600 text-xs'>
68+
{(p.authors && p.authors.length > 0 ? p.authors : [p.author])
69+
.slice(0, 2)
70+
.map((a) => a?.name)
71+
.join(', ')}
72+
{(p.authors && p.authors.length > 0 ? p.authors : [p.author]).length > 2 && (
73+
<>
74+
{' '}
75+
and {(p.authors && p.authors.length > 0 ? p.authors : [p.author]).length - 2}{' '}
76+
other
77+
{(p.authors && p.authors.length > 0 ? p.authors : [p.author]).length - 2 > 1
78+
? 's'
79+
: ''}
80+
</>
81+
)}
82+
</span>
83+
</div>
84+
</div>
85+
</div>
86+
</Link>
87+
))}
88+
</div>
89+
)
90+
}

apps/sim/app/_shell/providers/theme-provider.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ export function ThemeProvider({ children, ...props }: ThemeProviderProps) {
1212
pathname === '/' ||
1313
pathname.startsWith('/login') ||
1414
pathname.startsWith('/signup') ||
15+
pathname.startsWith('/reset-password') ||
1516
pathname.startsWith('/sso') ||
1617
pathname.startsWith('/terms') ||
1718
pathname.startsWith('/privacy') ||

apps/sim/app/_styles/globals.css

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -759,3 +759,24 @@ input[type="search"]::-ms-clear {
759759
--surface-elevated: #202020;
760760
}
761761
}
762+
763+
/**
764+
* Remove backticks from inline code in prose (Tailwind Typography default)
765+
*/
766+
.prose code::before,
767+
.prose code::after {
768+
content: none !important;
769+
}
770+
771+
/**
772+
* Remove underlines from heading anchor links in prose
773+
*/
774+
.prose h1 a,
775+
.prose h2 a,
776+
.prose h3 a,
777+
.prose h4 a,
778+
.prose h5 a,
779+
.prose h6 a {
780+
text-decoration: none !important;
781+
color: inherit !important;
782+
}

apps/sim/app/api/auth/accounts/route.ts

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,17 @@ export async function GET(request: NextRequest) {
3232
.from(account)
3333
.where(and(...whereConditions))
3434

35-
return NextResponse.json({ accounts })
35+
// Use the user's email as the display name (consistent with credential selector)
36+
const userEmail = session.user.email
37+
38+
const accountsWithDisplayName = accounts.map((acc) => ({
39+
id: acc.id,
40+
accountId: acc.accountId,
41+
providerId: acc.providerId,
42+
displayName: userEmail || acc.providerId,
43+
}))
44+
45+
return NextResponse.json({ accounts: accountsWithDisplayName })
3646
} catch (error) {
3747
logger.error('Failed to fetch accounts', { error })
3848
return NextResponse.json({ error: 'Internal server error' }, { status: 500 })

apps/sim/app/api/auth/forget-password/route.test.ts

Lines changed: 31 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,10 @@
66
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
77
import { createMockRequest, setupAuthApiMocks } from '@/app/api/__test-utils__/utils'
88

9+
vi.mock('@/lib/core/utils/urls', () => ({
10+
getBaseUrl: vi.fn(() => 'https://app.example.com'),
11+
}))
12+
913
describe('Forget Password API Route', () => {
1014
beforeEach(() => {
1115
vi.resetModules()
@@ -15,7 +19,7 @@ describe('Forget Password API Route', () => {
1519
vi.clearAllMocks()
1620
})
1721

18-
it('should send password reset email successfully', async () => {
22+
it('should send password reset email successfully with same-origin redirectTo', async () => {
1923
setupAuthApiMocks({
2024
operations: {
2125
forgetPassword: { success: true },
@@ -24,7 +28,7 @@ describe('Forget Password API Route', () => {
2428

2529
const req = createMockRequest('POST', {
2630
27-
redirectTo: 'https://example.com/reset',
31+
redirectTo: 'https://app.example.com/reset',
2832
})
2933

3034
const { POST } = await import('@/app/api/auth/forget-password/route')
@@ -39,12 +43,36 @@ describe('Forget Password API Route', () => {
3943
expect(auth.auth.api.forgetPassword).toHaveBeenCalledWith({
4044
body: {
4145
42-
redirectTo: 'https://example.com/reset',
46+
redirectTo: 'https://app.example.com/reset',
4347
},
4448
method: 'POST',
4549
})
4650
})
4751

52+
it('should reject external redirectTo URL', async () => {
53+
setupAuthApiMocks({
54+
operations: {
55+
forgetPassword: { success: true },
56+
},
57+
})
58+
59+
const req = createMockRequest('POST', {
60+
61+
redirectTo: 'https://evil.com/phishing',
62+
})
63+
64+
const { POST } = await import('@/app/api/auth/forget-password/route')
65+
66+
const response = await POST(req)
67+
const data = await response.json()
68+
69+
expect(response.status).toBe(400)
70+
expect(data.message).toBe('Redirect URL must be a valid same-origin URL')
71+
72+
const auth = await import('@/lib/auth')
73+
expect(auth.auth.api.forgetPassword).not.toHaveBeenCalled()
74+
})
75+
4876
it('should send password reset email without redirectTo', async () => {
4977
setupAuthApiMocks({
5078
operations: {

0 commit comments

Comments
 (0)