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
4 changes: 4 additions & 0 deletions .agents/skills/emcn-design-review/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,10 @@ Use `ChipConfirmModal` (title "Delete/Remove {ItemType}", `confirm={{ label, onC

Default: `size-[14px]`. Color: `text-[var(--text-icon)]`. Scale: 14px > 16px > 12px > 20px. Use the `size-*` shorthand — flag `h-[Npx] w-[Npx]` and `h-N w-N` pairs as refactor targets.

## Mobile

Check at 320px and 390px with touch, then desktop and fullscreen. Use `Chip`/`ChipLink`'s `mobileIconOnly` for familiar mobile toolbar actions, preserving accessible names; keep labels for ambiguous choices. Keep navigation, primary actions, and dismissal reachable without hover or dragging; aim for 44px touch targets with compact visible icons/button faces and 16px editable text. Match behavior to the actual container or viewport breakpoint. Contain horizontal scrolling to intentional tables/code, fit overlays to the dynamic viewport, and keep the composer/actions reachable with the keyboard open. Reuse EMCN tokens and brief motion with reduced-motion support; preserve desktop geometry.

## Anti-patterns to flag

- Raw `<button>`/`<input>`, or legacy `Input`/`Textarea`/`Modal`, instead of the canonical chip components (`ChipInput`/`ChipTextarea`/`ChipModal`)
Expand Down
5 changes: 3 additions & 2 deletions .claude/rules/sim-testing.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,9 @@ contracts, and demonstrated regressions.
(`sim_test`); `TEST_REDIS_URL` must be loopback. `packages/db/testing/test-infrastructure.ts` owns
those checks. Isolate with a unique schema or generated IDs, and clean up in `afterAll`.
- Integration files run one at a time against one shared database. A new `*.integration.ts` is
picked up by CI with no workflow change, and the run writes `test-results/integration.json`, which
CI uploads. Never add a passing suite to the quarantine list in `apps/sim/vitest.config.ts`.
picked up by CI with no workflow change, and the run writes the JSON report configured in
`vitest.shared.ts`, which CI uploads. Never add a passing suite to the quarantine list in
`apps/sim/vitest.config.ts`.
- `bun run test:integration` starts disposable Postgres and Redis containers, provisions the schema,
and runs both workspaces; pass filenames to narrow the `apps/sim` run.

Expand Down
5 changes: 3 additions & 2 deletions .cursor/rules/sim-testing.mdc
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,9 @@ contracts, and demonstrated regressions.
(`sim_test`); `TEST_REDIS_URL` must be loopback. `packages/db/testing/test-infrastructure.ts` owns
those checks. Isolate with a unique schema or generated IDs, and clean up in `afterAll`.
- Integration files run one at a time against one shared database. A new `*.integration.ts` is
picked up by CI with no workflow change, and the run writes `test-results/integration.json`, which
CI uploads. Never add a passing suite to the quarantine list in `apps/sim/vitest.config.ts`.
picked up by CI with no workflow change, and the run writes the JSON report configured in
`vitest.shared.ts`, which CI uploads. Never add a passing suite to the quarantine list in
`apps/sim/vitest.config.ts`.
- `bun run test:integration` starts disposable Postgres and Redis containers, provisions the schema,
and runs both workspaces; pass filenames to narrow the `apps/sim` run.

Expand Down
17 changes: 15 additions & 2 deletions .github/scripts/http-e2e.sh
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
#!/usr/bin/env bash
# Runs one end-to-end suite group over real HTTP, each against its own `next dev` app.
#
# Usage: http-e2e.sh <scim|cli|stop-after|desktop-inbox> (run from apps/sim)
# Usage: http-e2e.sh <scim|cli|stop-after|desktop-inbox|mobile> (run from apps/sim)
#
# The job provides DATABASE_URL, BETTER_AUTH_SECRET and ENCRYPTION_KEY; each group sets the rest of
# its app's environment here. Reports and server logs land in $RUNNER_TEMP/e2e.
Expand All @@ -17,7 +17,7 @@
# telemetry flush runs detached and still writes .next/dev).
set -euo pipefail

group=${1:?usage: http-e2e.sh <scim|cli|stop-after|desktop-inbox>}
group=${1:?usage: http-e2e.sh <scim|cli|stop-after|desktop-inbox|mobile>}
report_dir="$RUNNER_TEMP/e2e"
ready_timeout_seconds=300
mkdir -p "$report_dir"
Expand Down Expand Up @@ -156,6 +156,19 @@ case "$group" in
bun run test:desktop-inbox:e2e
;;

mobile)
export NEXT_PUBLIC_FORCE_HOSTED=false
export INTERNAL_API_SECRET=mobile-http-ci-local-secret-at-least-32-characters
export NODE_OPTIONS="${NODE_OPTIONS:+$NODE_OPTIONS }--max-old-space-size=12288"
bunx --no-install playwright install --with-deps chromium webkit
start_app mobile 3024 'mobile browser' record-http-status
MOBILE_E2E_BASE_URL="$NEXT_PUBLIC_APP_URL" \
MOBILE_E2E_DATABASE_URL="$DATABASE_URL" \
MOBILE_E2E_AUTH_SECRET="$BETTER_AUTH_SECRET" \
MOBILE_E2E_REPORT_PATH="$report_dir/mobile-e2e-report.json" \
bun run test:mobile:e2e
;;

*)
echo "::error::Unknown end-to-end group: $group" >&2
exit 2
Expand Down
8 changes: 5 additions & 3 deletions .github/workflows/checks.yml
Original file line number Diff line number Diff line change
Expand Up @@ -138,12 +138,12 @@ jobs:
# The suites exercise HTTP behavior rather than a provisioning path, so they run once, against
# the production (migrate) path. Each group's app environment lives in http-e2e.sh.
#
# SCIM runs two suites against a hosted app and is the longest group, so it keeps the 8 vCPU
# runner; the others boot a smaller self-hosted app and fit on 4.
# SCIM's hosted suites and the mobile browser matrix use 8 vCPU; the other
# self-hosted suites fit on 4.
e2e:
name: e2e (${{ matrix.group }})
runs-on: ${{ (vars.CI_PROVIDER == '' || vars.CI_PROVIDER == 'blacksmith') && matrix.runner || 'ubuntu-latest' }}
timeout-minutes: 20
timeout-minutes: ${{ matrix.group == 'mobile' && 40 || 20 }}
strategy:
fail-fast: false
matrix:
Expand All @@ -156,6 +156,8 @@ jobs:
runner: blacksmith-4vcpu-ubuntu-2404
- group: desktop-inbox
runner: blacksmith-4vcpu-ubuntu-2404
- group: mobile
runner: blacksmith-8vcpu-ubuntu-2404
services:
postgres:
image: pgvector/pgvector:pg17
Expand Down
2 changes: 1 addition & 1 deletion apps/desktop/src/renderer/credential-picker/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ function CredentialPicker({ configuration, api }: CredentialPickerProps) {
align='start'
sideOffset={0}
avoidCollisions={false}
className='w-[320px] max-w-none'
className='w-[320px] max-w-none max-md:max-w-none!'
Comment thread
waleedlatif1 marked this conversation as resolved.
aria-label='Saved passwords'
onOpenAutoFocus={(event) => {
event.preventDefault()
Expand Down
32 changes: 26 additions & 6 deletions apps/sim/app/(interfaces)/chat/[identifier]/chat.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
'use client'

import { type RefObject, useCallback, useMemo, useRef, useState } from 'react'
import { type ReactNode, type RefObject, useCallback, useMemo, useRef, useState } from 'react'
import { usePrefersReducedMotion } from '@sim/emcn'
import { createLogger } from '@sim/logger'
import { generateId } from '@sim/utils/id'
import {
Expand All @@ -24,6 +25,7 @@ import { useChatStreaming } from '@/app/(interfaces)/chat/hooks'
import SSOAuth from '@/ee/sso/components/sso-auth'
import { useDeployedChatConfig } from '@/hooks/queries/chats'
import { useGitHubStars } from '@/hooks/queries/github-stars'
import { useMobileViewport } from '@/hooks/use-mobile-viewport'

const logger = createLogger('ChatClient')

Expand Down Expand Up @@ -51,7 +53,25 @@ function fileToBase64(file: File): Promise<string> {
})
}

interface ChatViewportProps {
children: ReactNode
}

function ChatViewport({ children }: ChatViewportProps) {
const ref = useRef<HTMLDivElement>(null)
useMobileViewport({ ref })
return (
<div
ref={ref}
className='light desktop-title-bar-page fixed inset-0 z-[var(--z-dropdown)] flex flex-col bg-[var(--bg)] text-[var(--text-primary)] max-md:top-[var(--mobile-viewport-top,0px)] max-md:bottom-auto max-md:h-[var(--mobile-viewport-height,100dvh)] max-md:min-h-0!'
>
{children}
</div>
)
}

export default function ChatClient({ identifier }: { identifier: string }) {
const prefersReducedMotion = usePrefersReducedMotion()
const [messages, setMessages] = useState<ChatMessage[]>([])
const [isLoading, setIsLoading] = useState(false)
const messagesEndRef = useRef<HTMLDivElement>(null)
Expand Down Expand Up @@ -95,7 +115,7 @@ export default function ChatClient({ identifier }: { identifier: string }) {
* With `force` (jump button), re-pins to bottom.
*/
const scrollToBottom = (options?: { behavior?: ScrollBehavior; force?: boolean }) => {
const behavior = options?.behavior ?? 'smooth'
const behavior = prefersReducedMotion ? 'auto' : (options?.behavior ?? 'smooth')
const force = options?.force === true
if (!force && !stickToBottomRef.current) return
if (!messagesEndRef.current) return
Expand Down Expand Up @@ -125,7 +145,7 @@ export default function ChatClient({ identifier }: { identifier: string }) {

container.scrollTo({
top: container.scrollTop + messageRect.top - containerRect.top,
behavior: 'smooth',
behavior: prefersReducedMotion ? 'auto' : 'smooth',
})
}

Expand Down Expand Up @@ -309,7 +329,7 @@ export default function ChatClient({ identifier }: { identifier: string }) {
}

return (
<div className='light desktop-title-bar-page fixed inset-0 z-[var(--z-dropdown)] flex flex-col bg-[var(--bg)] text-[var(--text-primary)]'>
<ChatViewport>
<DesktopTitleBarLane />
<ChatHeader chatConfig={chatConfig} starCount={starCount} />

Expand All @@ -323,7 +343,7 @@ export default function ChatClient({ identifier }: { identifier: string }) {
chatConfig={chatConfig}
/>

<div className='relative p-3 pb-4 md:p-4 md:pb-6'>
<div className='relative shrink-0 md:p-4 md:pb-6'>
<div className='relative mx-auto max-w-3xl md:max-w-[748px]'>
<ChatInput
onSubmit={(value, files) => {
Expand All @@ -334,6 +354,6 @@ export default function ChatClient({ identifier }: { identifier: string }) {
/>
</div>
</div>
</div>
</ChatViewport>
)
}
20 changes: 11 additions & 9 deletions apps/sim/app/(interfaces)/chat/components/header/header.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
'use client'

import { SimWordmark } from '@sim/emcn'
import { OverflowText, SimWordmark } from '@sim/emcn'
import Image from 'next/image'
import Link from 'next/link'
import { GithubIcon } from '@/components/icons'
Expand All @@ -27,33 +27,35 @@ export function ChatHeader({ chatConfig, starCount }: ChatHeaderProps) {
return (
<nav
aria-label='Chat navigation'
className='flex w-full items-center justify-between px-4 pt-3 pb-[21px] sm:px-8 sm:pt-[8.5px] md:px-[44px] md:pt-4'
className='flex w-full shrink-0 items-center justify-between gap-3 px-4 pt-3 pb-[21px] sm:px-8 sm:pt-[8.5px] md:px-[44px] md:pt-4'
>
<div className='flex items-center gap-[34px]'>
<div className='flex items-center gap-3'>
<div className='min-w-0 flex-1'>
<div className='flex min-w-0 items-center gap-3'>
{customImage && (
<Image
src={customImage}
alt={`${chatConfig?.title || 'Chat'} logo`}
width={24}
height={24}
unoptimized
className='size-6 rounded-md object-cover'
className='size-6 shrink-0 rounded-md object-cover'
/>
)}
<h2 className='text-[var(--text-primary)] text-lg'>
{chatConfig?.customizations?.headerText || chatConfig?.title || 'Chat'}
<h2 className='min-w-0 text-[var(--text-primary)] text-lg'>
<OverflowText
label={chatConfig?.customizations?.headerText || chatConfig?.title || 'Chat'}
/>
</h2>
</div>
</div>

{!brand.logoUrl && (
<div className='flex items-center gap-4'>
<div className='flex shrink-0 items-center gap-4'>
<a
href='https://github.com/simstudioai/sim'
target='_blank'
rel='noopener noreferrer'
className='flex items-center gap-2 text-[var(--text-muted)] transition-colors hover-hover:text-[var(--text-primary)]'
className='hidden items-center gap-2 text-[var(--text-muted)] transition-colors hover-hover:text-[var(--text-primary)] md:flex'
aria-label={`GitHub repository - ${starCount} stars`}
>
<GithubIcon className='size-[16px]' aria-hidden='true' />
Expand Down
Loading
Loading