Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
4f98438
improvement(ui): restore smooth streaming animation, fix follow-up au…
waleedlatif1 Apr 14, 2026
aba72b8
fix(ui): restore delayed animation, handle tilde fences, fix follow-u…
waleedlatif1 Apr 14, 2026
77f2b27
fix(ui): extract useStreamingReveal to followup, keep cleanup changes
waleedlatif1 Apr 14, 2026
c68263f
fix(ui): restore hydratedStreamingRef for reconnect path order-of-ops
waleedlatif1 Apr 14, 2026
0ecd6bd
fix(ui): restore full hydratedStreamingRef effect for reconnect path
waleedlatif1 Apr 14, 2026
335c295
fix(ui): use hover-hover prefix on CopyCodeButton callers to correctl…
waleedlatif1 Apr 14, 2026
b6ab71d
fix(logs): remove destructive color from cancel execution menu item
waleedlatif1 Apr 14, 2026
03f6e74
feat(logs): optimistic cancelling status on cancel execution
waleedlatif1 Apr 14, 2026
316b6ac
feat(logs): allow cancellation of pending (paused) executions
waleedlatif1 Apr 14, 2026
8b920dc
fix(hitl): cancel paused executions directly in DB
waleedlatif1 Apr 14, 2026
394be59
upgrade turbo
waleedlatif1 Apr 14, 2026
42d17d6
test(hitl): update cancel route tests for paused execution cancellation
waleedlatif1 Apr 14, 2026
85d9fd6
fix(hitl): set endedAt when cancelling paused execution
waleedlatif1 Apr 14, 2026
6780e00
fix(hitl): emit execution:cancelled event to canvas when cancelling p…
waleedlatif1 Apr 14, 2026
15360c3
fix(hitl): isolate cancelPausedExecution failure from successful canc…
waleedlatif1 Apr 14, 2026
abf4b5a
fix(hitl): add .catch() to fire-and-forget event buffer calls in canc…
waleedlatif1 Apr 14, 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
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import React, { type HTMLAttributes, memo, type ReactNode, useMemo } from 'react'
import { Streamdown } from 'streamdown'
import 'streamdown/styles.css'
import { Tooltip } from '@/components/emcn'
import { CopyCodeButton } from '@/components/ui/copy-code-button'
import { CopyCodeButton, Tooltip } from '@/components/emcn'
import { extractTextContent } from '@/lib/core/utils/react-node-text'

export function LinkWithPreview({ href, children }: { href: string; children: React.ReactNode }) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,7 @@ import 'prismjs/components/prism-bash'
import 'prismjs/components/prism-css'
import 'prismjs/components/prism-markup'
import '@/components/emcn/components/code/code.css'
import { Checkbox, highlight, languages } from '@/components/emcn'
import { CopyCodeButton } from '@/components/ui/copy-code-button'
import { Checkbox, CopyCodeButton, highlight, languages } from '@/components/emcn'
import { cn } from '@/lib/core/utils/cn'
import { extractTextContent } from '@/lib/core/utils/react-node-text'
import {
Expand Down Expand Up @@ -148,7 +147,7 @@ const MARKDOWN_COMPONENTS = {
<span className='text-[var(--text-tertiary)] text-xs'>{language || 'code'}</span>
<CopyCodeButton
code={codeString}
className='text-[var(--text-tertiary)] hover:bg-[var(--surface-5)] hover:text-[var(--text-secondary)]'
className='-mr-2 text-[var(--text-tertiary)] hover:bg-[var(--surface-5)] hover:text-[var(--text-secondary)]'
/>
</div>
<div className='code-editor-theme bg-[var(--surface-5)] dark:bg-[var(--code-bg)]'>
Expand Down Expand Up @@ -265,12 +264,7 @@ export function ChatContent({
useEffect(() => {
const handler = (e: Event) => {
const { type, id, title } = (e as CustomEvent).detail
const RESOURCE_TYPE_MAP: Record<string, string> = {}
onWorkspaceResourceSelectRef.current?.({
type: RESOURCE_TYPE_MAP[type] || type,
id,
title: title || id,
})
onWorkspaceResourceSelectRef.current?.({ type, id, title: title || id })
}
window.addEventListener('wsres-click', handler)
return () => window.removeEventListener('wsres-click', handler)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -176,7 +176,7 @@ export function MothershipChat({
blocks={msg.contentBlocks || []}
fallbackContent={msg.content}
isStreaming={isThisStreaming}
onOptionSelect={isLastMessage && !isStreamActive ? onSubmit : undefined}
onOptionSelect={isLastMessage ? onSubmit : undefined}
onWorkspaceResourceSelect={onWorkspaceResourceSelect}
/>
{!isThisStreaming && (msg.content || msg.contentBlocks?.length) && (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -232,6 +232,7 @@ export function UserInput({
const canSubmit = (value.trim().length > 0 || hasFiles) && !isSending

const valueRef = useRef(value)
valueRef.current = value
const sttPrefixRef = useRef('')

const handleTranscript = useCallback((text: string) => {
Expand Down Expand Up @@ -271,10 +272,6 @@ export function UserInput({
const isSendingRef = useRef(isSending)
isSendingRef.current = isSending

useEffect(() => {
valueRef.current = value
}, [value])

const textareaRef = mentionMenu.textareaRef
const wasSendingRef = useRef(false)
const atInsertPosRef = useRef<number | null>(null)
Expand Down Expand Up @@ -358,9 +355,7 @@ export function UserInput({
}
// Reset after batch so the next non-drop insert uses the cursor position
atInsertPosRef.current = null
} catch {
// Invalid JSON — ignore
}
} catch {}
textareaRef.current?.focus()
return
}
Expand All @@ -372,9 +367,7 @@ export function UserInput({
const resource = JSON.parse(resourceJson) as MothershipResource
handleResourceSelect(resource)
atInsertPosRef.current = null
} catch {
// Invalid JSON — ignore
}
} catch {}
textareaRef.current?.focus()
return
}
Expand Down
38 changes: 16 additions & 22 deletions apps/sim/app/workspace/[workspaceId]/home/home.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { useCallback, useEffect, useRef, useState } from 'react'
import { createLogger } from '@sim/logger'
import { useParams, useRouter, useSearchParams } from 'next/navigation'
import { usePostHog } from 'posthog-js/react'
import { Button } from '@/components/emcn'
import { PanelLeft } from '@/components/emcn/icons'
import { useSession } from '@/lib/auth/auth-client'
import {
Expand Down Expand Up @@ -33,6 +34,7 @@ export function Home({ chatId }: HomeProps = {}) {
const { data: session } = useSession()
const posthog = usePostHog()
const posthogRef = useRef(posthog)
posthogRef.current = posthog
const [initialPrompt, setInitialPrompt] = useState('')
const hasCheckedLandingStorageRef = useRef(false)
const initialViewInputRef = useRef<HTMLDivElement>(null)
Expand Down Expand Up @@ -99,19 +101,12 @@ export function Home({ chatId }: HomeProps = {}) {
return
}

// const templateId = LandingTemplateStorage.consume()
// if (templateId) {
// logger.info('Retrieved landing page template, redirecting to template detail')
// router.replace(`/workspace/${workspaceId}/templates/${templateId}?use=true`)
// return
// }

const prompt = LandingPromptStorage.consume()
if (prompt) {
logger.info('Retrieved landing page prompt, populating home input')
setInitialPrompt(prompt)
}
}, [createWorkflowFromLandingSeed, workspaceId, router])
}, [createWorkflowFromLandingSeed])

const wasSendingRef = useRef(false)

Expand All @@ -130,10 +125,6 @@ export function Home({ chatId }: HomeProps = {}) {
setIsResourceCollapsed(true)
}, [clearWidth])

const expandResource = useCallback(() => {
setIsResourceCollapsed(false)
}, [])

const handleResourceEvent = useCallback(() => {
if (isResourceCollapsedRef.current) {
setIsResourceCollapsed(false)
Expand Down Expand Up @@ -224,10 +215,6 @@ export function Home({ chatId }: HomeProps = {}) {
return () => cancelAnimationFrame(id)
}, [resources])

useEffect(() => {
posthogRef.current = posthog
}, [posthog])

const handleStopGeneration = useCallback(() => {
captureEvent(posthogRef.current, 'task_generation_aborted', {
workspace_id: workspaceId,
Expand Down Expand Up @@ -299,9 +286,14 @@ export function Home({ chatId }: HomeProps = {}) {
const handleInitialContextRemove = useCallback(
(context: ChatContext) => {
const resolved = resolveResourceFromContext(context)
if (resolved) removeResource(resolved.type, resolved.id)
if (!resolved) return
removeResource(resolved.type, resolved.id)
const remaining = resources.filter((r) => !(r.type === resolved.type && r.id === resolved.id))
if (remaining.length === 0) {
collapseResource()
}
},
[resolveResourceFromContext, removeResource]
[resolveResourceFromContext, removeResource, resources, collapseResource]
Comment thread
waleedlatif1 marked this conversation as resolved.
Outdated
)

const handleWorkspaceResourceSelect = useCallback(
Expand Down Expand Up @@ -426,14 +418,16 @@ export function Home({ chatId }: HomeProps = {}) {

{isResourceCollapsed && (
<div className='absolute top-[8.5px] right-[16px]'>
<button
<Button
variant='ghost'
size={null}
type='button'
onClick={expandResource}
className='flex h-[30px] w-[30px] items-center justify-center rounded-[8px] hover-hover:bg-[var(--surface-active)]'
onClick={() => setIsResourceCollapsed(false)}
className='h-[30px] w-[30px] rounded-[8px] hover-hover:bg-[var(--surface-active)]'
aria-label='Expand resource view'
>
<PanelLeft className='h-[16px] w-[16px] text-[var(--text-icon)]' />
</button>
</Button>
</div>
)}
</div>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
'use client'

import { useCallback, useEffect, useRef, useState } from 'react'
import { Check, Copy } from '@/components/emcn'
import { Button, Check, Copy } from '@/components/emcn'
import { cn } from '@/lib/core/utils/cn'

interface CopyCodeButtonProps {
Expand All @@ -19,9 +19,7 @@ export function CopyCodeButton({ code, className }: CopyCodeButtonProps) {
setCopied(true)
if (timerRef.current) clearTimeout(timerRef.current)
timerRef.current = setTimeout(() => setCopied(false), 2000)
} catch {
// Clipboard write can fail when document lacks focus or permission is denied
}
} catch {}
}, [code])

useEffect(
Expand All @@ -32,15 +30,13 @@ export function CopyCodeButton({ code, className }: CopyCodeButtonProps) {
)

return (
<button
<Button
type='button'
variant='ghost'
onClick={handleCopy}
className={cn(
'flex items-center gap-1 rounded px-1.5 py-0.5 text-xs transition-colors',
className
)}
className={cn('flex items-center gap-1 rounded px-1.5 py-0.5 text-xs', className)}
>
{copied ? <Check className='size-3.5' /> : <Copy className='size-3.5' />}
</button>
</Button>
Comment thread
waleedlatif1 marked this conversation as resolved.
)
}
1 change: 1 addition & 0 deletions apps/sim/components/emcn/components/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ export {
highlight,
languages,
} from './code/code'
export { CopyCodeButton } from './code/copy-code-button'
export {
Combobox,
type ComboboxOption,
Expand Down
51 changes: 0 additions & 51 deletions apps/sim/lib/core/utils/browser-storage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,6 @@ export class BrowserStorage {

export const STORAGE_KEYS = {
LANDING_PAGE_PROMPT: 'sim_landing_page_prompt',
LANDING_PAGE_TEMPLATE: 'sim_landing_page_template',
LANDING_PAGE_WORKFLOW_SEED: 'sim_landing_page_workflow_seed',
WORKSPACE_RECENCY: 'sim_workspace_recency',
} as const
Expand Down Expand Up @@ -248,56 +247,6 @@ export class LandingPromptStorage {
}
}

/**
* Specialized utility for managing a template selection from the landing page.
* Stores the marketplace template ID so it can be consumed after signup.
*/
export class LandingTemplateStorage {
private static readonly KEY = STORAGE_KEYS.LANDING_PAGE_TEMPLATE

/**
* Store a template ID selected on the landing page
* @param templateId - The marketplace template UUID
*/
static store(templateId: string): boolean {
if (!templateId || templateId.trim().length === 0) {
return false
}

return BrowserStorage.setItem(LandingTemplateStorage.KEY, {
templateId: templateId.trim(),
timestamp: Date.now(),
})
}

/**
* Retrieve and consume the stored template ID
* @param maxAge - Maximum age in milliseconds (default: 24 hours)
*/
static consume(maxAge: number = 24 * 60 * 60 * 1000): string | null {
const data = BrowserStorage.getItem<{ templateId: string; timestamp: number } | null>(
LandingTemplateStorage.KEY,
null
)

if (!data || !data.templateId || !data.timestamp) {
return null
}

if (Date.now() - data.timestamp > maxAge) {
LandingTemplateStorage.clear()
return null
}

LandingTemplateStorage.clear()
return data.templateId
}

static clear(): boolean {
return BrowserStorage.removeItem(LandingTemplateStorage.KEY)
}
}

export interface LandingWorkflowSeed {
templateId: string
workflowName: string
Expand Down
Loading