Skip to content

Commit 76916e2

Browse files
committed
Give each route its loading geometry and stop a chunk re-rendering the transcript
Settings, chat and Search gain loading routes, and the People and Home skeletons match what replaces them. Runtime handlers keep one identity and what awaits a decision has its own context, so a settled turn stays rendered across a stream. The scroll observer tracks only the rows that came or went.
1 parent 586e0f1 commit 76916e2

34 files changed

Lines changed: 347 additions & 114 deletions

File tree

‎apps/sim/app/workspace/[workspaceId]/logs/search-params.ts‎

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,6 @@ import {
2020
* the raw input value (consumers trim on read).
2121
*/
2222

23-
export { parseAsDateString }
24-
2523
const DEFAULT_TIME_RANGE: TimeRange = 'All time'
2624

2725
const VALID_LEVELS = ['error', 'info', 'running', 'pending'] as const

‎apps/sim/hooks/queries/organization.ts‎

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,14 +30,16 @@ import {
3030
} from '@/lib/api/contracts/organization'
3131
import { client } from '@/lib/auth/client'
3232
import { workspaceCredentialKeys } from '@/hooks/queries/utils/credential-keys'
33-
import { organizationKeys } from '@/hooks/queries/utils/organization-keys'
33+
import {
34+
ORGANIZATION_ROSTER_STALE_TIME,
35+
organizationKeys,
36+
} from '@/hooks/queries/utils/organization-keys'
3437
import { organizationUsageKeys } from '@/hooks/queries/utils/organization-usage-keys'
3538
import { subscriptionKeys } from '@/hooks/queries/utils/subscription-keys'
3639
import { workspaceKeys } from '@/hooks/queries/workspace'
3740

3841
const invitationListsKey = ['invitations', 'list'] as const
3942

40-
export const ORGANIZATION_ROSTER_STALE_TIME = 30 * 1000
4143
export const ORGANIZATION_DETAIL_STALE_TIME = 30 * 1000
4244
export const ORGANIZATION_MEMBER_USAGE_LIMIT_STALE_TIME = 30 * 1000
4345
/**

‎apps/sim/hooks/queries/utils/organization-keys.ts‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
export const ORGANIZATION_ROSTER_STALE_TIME = 30 * 1000
2+
13
export const organizationKeys = {
24
all: ['organizations'] as const,
35
lists: () => [...organizationKeys.all, 'list'] as const,

‎apps/sim/platform/app/(shell)/_components/chat/cards/assistant-review.tsx‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import type {
66
AssistantReceipt,
77
AssistantReviewAction,
88
} from '@/platform/app/(shell)/_components/chat/model'
9-
import { useChatRuntime } from '@/platform/app/(shell)/_components/session/chat-runtime'
9+
import { useChatInteractions } from '@/platform/app/(shell)/_components/session/chat-runtime'
1010
import { Button, FailureNotice, InlineNotice, StatusTag } from '@/platform/design-system/components'
1111
import { Input, TextArea } from '@/platform/design-system/components/field/inputs'
1212

@@ -132,7 +132,7 @@ function Receipt({ receipt }: { receipt: AssistantReceipt }) {
132132
* the turn they belong to; failure to load is shown rather than read as nothing to review.
133133
*/
134134
export function AssistantReview({ turnId }: { turnId: string }) {
135-
const { review } = useChatRuntime()
135+
const { review } = useChatInteractions()
136136
const [edits, setEdits] = useState<Edits>({})
137137
const [failure, setFailure] = useState('')
138138
if (!review || review.turnId !== turnId) return null

‎apps/sim/platform/app/(shell)/_components/chat/cards/tool-approval.tsx‎

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,10 @@ import { generateId } from '@sim/utils/id'
55
import styles from '@/platform/app/(shell)/_components/chat/cards/cards.module.css'
66
import type { PlatformTool, ToolDecision } from '@/platform/app/(shell)/_components/chat/model'
77
import { usePlatformNotices } from '@/platform/app/(shell)/_components/notification-tray/notification-tray'
8-
import { useChatRuntime } from '@/platform/app/(shell)/_components/session/chat-runtime'
8+
import {
9+
useChatHandlers,
10+
useChatInteractions,
11+
} from '@/platform/app/(shell)/_components/session/chat-runtime'
912
import { ActionMenu, Button, Icon } from '@/platform/design-system/components'
1013
import { ActivityBody } from '@/platform/design-system/components/activity-row/activity-body'
1114

@@ -16,7 +19,8 @@ const BROWSER_TOOL = /^browser[_-]/
1619
* turn, until one of these is answered; a failed answer restores the buttons so it can be retried.
1720
*/
1821
export function ToolApproval({ tool }: { tool: PlatformTool }) {
19-
const { approvals, registerApproval, decideTools } = useChatRuntime()
22+
const { approvals } = useChatInteractions()
23+
const { registerApproval, decideTools } = useChatHandlers()
2024
const { notify } = usePlatformNotices()
2125
const [expired, setExpired] = useState(false)
2226
useEffect(() => registerApproval(tool.id), [registerApproval, tool.id])
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import styles from '@/platform/app/(shell)/_components/chat/conversation/conversation.module.css'
2+
3+
/** A conversation's frame while the route decides whether the viewer may open it. */
4+
export function ConversationSkeleton() {
5+
return (
6+
<div className={styles.chat} role='status' aria-label='Loading conversation' aria-busy='true'>
7+
<header className={styles.header} data-desktop-drag='' />
8+
<div className={styles.reading} />
9+
<div className={styles.dock}>
10+
<div className={styles.dockInner}>
11+
<div className={styles.composerPlaceholder} />
12+
</div>
13+
</div>
14+
</div>
15+
)
16+
}

‎apps/sim/platform/app/(shell)/_components/chat/conversation/conversation.module.css‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,3 +85,8 @@
8585
font-weight: var(--font-weight-medium);
8686
color: var(--ui-ink);
8787
}
88+
.composerPlaceholder {
89+
block-size: var(--spacing-132);
90+
border-radius: var(--ui-control-radius);
91+
background: var(--control-muted-fill);
92+
}

‎apps/sim/platform/app/(shell)/_components/chat/conversation/conversation.tsx‎

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
'use client'
22

3-
import { type ReactNode, useLayoutEffect, useRef, useState } from 'react'
3+
import { memo, type ReactNode, useLayoutEffect, useRef, useState } from 'react'
44
import styles from '@/platform/app/(shell)/_components/chat/conversation/conversation.module.css'
55
import { QueuedMessages } from '@/platform/app/(shell)/_components/chat/conversation/queued-messages'
66
import { ResponseMessage } from '@/platform/app/(shell)/_components/chat/message/response-message'
@@ -19,7 +19,8 @@ import {
1919
} from '@/platform/design-system/components'
2020
import { useScrollEdges } from '@/platform/design-system/components/scroll-area/use-scroll-edges'
2121

22-
function Turn({ turn }: { turn: PlatformTurn }) {
22+
/** A settled turn keeps its identity, so only the streaming one renders per chunk. */
23+
const Turn = memo(function Turn({ turn }: { turn: PlatformTurn }) {
2324
if (turn.role === 'user') return <UserMessage turn={turn} />
2425
if (turn.role === 'automated')
2526
return (
@@ -28,7 +29,7 @@ function Turn({ turn }: { turn: PlatformTurn }) {
2829
</p>
2930
)
3031
return <ResponseMessage turn={turn} />
31-
}
32+
})
3233

3334
/** One exchange per article: a person's message and what answered it. */
3435
function groupExchanges(turns: readonly PlatformTurn[]): PlatformTurn[][] {

‎apps/sim/platform/app/(shell)/_components/chat/message/message-segments.tsx‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import type {
1010
PlatformCredentialSubmission,
1111
PlatformSegment,
1212
} from '@/platform/app/(shell)/_components/chat/model'
13-
import { useChatRuntime } from '@/platform/app/(shell)/_components/session/chat-runtime'
13+
import { useChatHandlers } from '@/platform/app/(shell)/_components/session/chat-runtime'
1414
import { Button, InlineNotice } from '@/platform/design-system/components'
1515

1616
export interface CredentialCardProps {
@@ -36,7 +36,7 @@ const Cards = createContext<ChatCards | null>(null)
3636
export const ChatCardsProvider = Cards.Provider
3737

3838
function Segment({ segment }: { segment: PlatformSegment }) {
39-
const runtime = useChatRuntime()
39+
const runtime = useChatHandlers()
4040
const cards = useContext(Cards)
4141
switch (segment.kind) {
4242
case 'markdown':

‎apps/sim/platform/app/(shell)/_components/chat/message/response-actions.tsx‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { useState } from 'react'
44
import styles from '@/platform/app/(shell)/_components/chat/message/message.module.css'
55
import { MessageTime } from '@/platform/app/(shell)/_components/chat/message/message-time'
66
import type { PlatformTurn } from '@/platform/app/(shell)/_components/chat/model'
7-
import { useChatRuntime } from '@/platform/app/(shell)/_components/session/chat-runtime'
7+
import { useChatHandlers } from '@/platform/app/(shell)/_components/session/chat-runtime'
88
import {
99
ActionMenu,
1010
CopyButton,
@@ -24,7 +24,7 @@ interface ResponseActionsProps {
2424

2525
export function ResponseActions({ turn }: ResponseActionsProps) {
2626
const { copyText: text, sentAt } = turn
27-
const { rate, fork } = useChatRuntime()
27+
const { rate, fork } = useChatHandlers()
2828
const [feedback, setFeedback] = useState<Rating | null>(null)
2929
const [rating, setRating] = useState<Rating | null>(null)
3030
const [details, setDetails] = useState('')

0 commit comments

Comments
 (0)