Skip to content

Commit f1498f5

Browse files
committed
Apply the review: settled turns keep their identity, and the seam's edges tighten
A turn that did not change now reuses its object, which is what lets the transcript memo hit. Prefetches share prefetchRequestJson, Modal's step back is onBack, a shimmed store names itself when subscribed to, and a server-rendered date waits for the zone both renders agree on. The closure check also covers ee views. The DELETE for a search source carries its URL in the body, and a shared mock and a misplaced contract comment now match the code.
1 parent 9b4e3a8 commit f1498f5

38 files changed

Lines changed: 213 additions & 241 deletions

File tree

‎apps/sim/app/api/organizations/[id]/search/history/sources/route.ts‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,8 @@ export const DELETE = defineInternalJsonRoute({
1616
operation: searchHistoryOperations.removeSource,
1717
rateLimit: internalRateLimits.user({ bucketName: 'search-history-write' }),
1818
errorPolicy: internalOrchestrationErrorPolicy,
19-
mapInput: ({ params, query }) => ({ organizationId: params.id, url: query.url }),
19+
parseOptions: { maxBodyBytes: 24 * 1024 },
20+
mapInput: ({ params, body }) => ({ organizationId: params.id, url: body.url }),
2021
useCase: removeSearchHistorySource,
2122
staticResponseHeaders: { 'Cache-Control': 'private, no-store' },
2223
})

‎apps/sim/hooks/queries/search-history.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ export function useRemoveSearchHistorySource(organizationId: string, userId: str
5959
mutationFn: (url: string) =>
6060
requestJson(removeSearchHistorySourceContract, {
6161
params: { id: organizationId },
62-
query: { url },
62+
body: { url },
6363
}),
6464
onMutate: async (url) => {
6565
await queryClient.cancelQueries({ queryKey })

‎apps/sim/lib/api/contracts/knowledge/search-history.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,6 @@ export const removeSearchHistorySourceContract = defineRouteContract({
5858
method: 'DELETE',
5959
path: '/api/organizations/[id]/search/history/sources',
6060
params: searchHistoryParamsSchema,
61-
query: z.object({ url: z.string().min(1).max(4096) }),
61+
body: z.object({ url: z.string().min(1).max(4096) }),
6262
response: { mode: 'json', schema: z.object({ success: z.literal(true) }) },
6363
})

‎apps/sim/lib/api/contracts/permission-groups.ts‎

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -361,10 +361,6 @@ export const bulkAddPermissionGroupMembersContract = defineRouteContract({
361361
},
362362
})
363363

364-
/**
365-
* List the workspaces belonging to an organization, used to populate the
366-
* workspace multi-select when scoping a permission group to specific workspaces.
367-
*/
368364
const permissionGroupEditorCatalogSchema = z.object({
369365
/** The allowlist universe. `hidden` decides drawing only; a hidden row stays part of every save. */
370366
blocks: z.array(
@@ -409,6 +405,10 @@ export const getPermissionGroupEditorCatalogContract = defineRouteContract({
409405
response: { mode: 'json', schema: permissionGroupEditorCatalogSchema },
410406
})
411407

408+
/**
409+
* List the workspaces belonging to an organization, used to populate the
410+
* workspace multi-select when scoping a permission group to specific workspaces.
411+
*/
412412
export const listOrganizationWorkspacesContract = defineRouteContract({
413413
method: 'GET',
414414
path: '/api/organizations/[id]/workspaces',

‎apps/sim/platform/AGENTS.md‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ Use `ActivityRow` for a first-line glyph or avatar, title/detail and centered tr
3131

3232
Measure the actual scroll viewport with `ScrollArea`, or attach `useScrollEdges` directly to a native or Base UI scroll owner. Use the shared 16px mask only on edges with more content, updating after scroll, resize, input, reset or filtering. Platform and Settings navigation hide the native scrollbar and keep their edge fades, keyboard, wheel and touch scrolling. Other scroll areas keep their existing scrollbar behavior. Insets establish resting layout; they cannot substitute for an edge fade. Home widgets and notification panels use `--ui-widget-max-size` as a ceiling, not a forced height. Keep the dashboard free to scroll and let each overflowing list or schedule scroll independently. Plain tooltips preview at most three lines; retain the complete accessible text. A structured people popover owns its content and uses the dropdown’s popup padding and row insets.
3333

34-
Form dialogs use `Modal` or `MultiStepModal`. Pass a title, content, action labels and handlers. A confirm handler that returns a promise holds the dialog open as pending, closes it on success, and keeps the draft with an inline message on failure; nothing dismisses it while pending. Returning `false` keeps it open at the dialog's next stage, and `cancelLabel`/`onCancel` turn the quiet footer action into a step back. The shared borderless surface positions its title at the logical start and supplies one quiet Cancel/Back. Command search and previews use the same shadow and dismissal layer. Form controls cannot construct or style a form header, footer or Cancel button. Opening a dialog or advancing its step focuses the first available field. Confirmation dialogs focus Cancel, and read-only previews focus Close. A nested dialog handles Escape one layer at a time. While a form is open, the global search shortcut leaves its draft and keyboard focus in place. Closing returns focus to the invoking control. Textareas scroll or grow within their owner and never show a manual resize handle. Recipient fields use the textarea’s height, draft typography and inset. Committed addresses use compact caption text and an always-visible neutral ×, like a panel tab. The remove button sits inside the tag beside its label; long addresses yield space to the button. Comma or Enter commits a draft, pasting a list commits its addresses, and Backspace from an empty draft removes the last tag.
34+
Form dialogs use `Modal` or `MultiStepModal`. Pass a title, content, action labels and handlers. A confirm handler that returns a promise holds the dialog open as pending, closes it on success, and keeps the draft with an inline message on failure; nothing dismisses it while pending. Returning `false` keeps it open at the dialog's next stage, and `onBack` turns the quiet footer action into a step back. The shared borderless surface positions its title at the logical start and supplies one quiet Cancel/Back. Command search and previews use the same shadow and dismissal layer. Form controls cannot construct or style a form header, footer or Cancel button. Opening a dialog or advancing its step focuses the first available field. Confirmation dialogs focus Cancel, and read-only previews focus Close. A nested dialog handles Escape one layer at a time. While a form is open, the global search shortcut leaves its draft and keyboard focus in place. Closing returns focus to the invoking control. Textareas scroll or grow within their owner and never show a manual resize handle. Recipient fields use the textarea’s height, draft typography and inset. Committed addresses use compact caption text and an always-visible neutral ×, like a panel tab. The remove button sits inside the tag beside its label; long addresses yield space to the button. Comma or Enter commits a draft, pasting a list commits its addresses, and Backspace from an empty draft removes the last tag.
3535

3636
Use `useClipboard` / `CopyButton` for successful copy feedback and `useFeedbackExpiry` for temporary status. Copied indicators and transient notices dwell for four seconds; repeated events restart the timer. Pause an actionable notice during hover or focus and keep its action available in the stack afterward. Icon color changes use `--ui-motion-color` (120ms, zero under reduced motion). Do not put literal timers or CSS durations in product components.
3737

@@ -64,7 +64,7 @@ Each `lib/data/<domain>.ts` wraps the existing hook and maps its contract shape
6464

6565
Views that fixtures also render stay prop-driven. A route composes `data` and a named `actions` interface, filled by session state on fixtures and by mutations on live data. A surface that exists only on live data, such as a Settings section, People or Search, reads its domain's hooks from `lib/data` directly. No fixture branch inside a view. Hide what has no backend and record it in the [parity ledger](docs/org-parity.md); never fake it. Distinguish loading, empty and failure. A list reads `status` rather than treating empty as loading.
6666

67-
A server read goes through `serverRequestJson`, which gives every read a deadline. A first-paint read lives in `lib/data/prefetch-<domain>.ts` under the hook's own key and `staleTime`, with `prefetchSignal()`; it may never block or fail the page, so the client fetches what it gave up. `resolvePlatformMode` decides each server entry once: `fixture` with `SIM_PLATFORM_FIXTURES=1`, `live` with `SIM_PLATFORM_API_ORIGIN`, otherwise `none`, today's empty rendering. Load the seam with a dynamic import behind the `live` check, so fixture servers never evaluate it. The viewer's organization comes from membership on the server, read once per request by `readOrganizationSurface`. Platform URLs carry no organization.
67+
A server read goes through `serverRequestJson`, which gives every read a deadline. A first-paint read lives in `lib/data/prefetch-<domain>.ts` under the hook's own key and `staleTime`, through `prefetchRequestJson`; it may never block or fail the page, so the client fetches what it gave up. `resolvePlatformMode` decides each server entry once: `fixture` with `SIM_PLATFORM_FIXTURES=1`, `live` with `SIM_PLATFORM_API_ORIGIN`, otherwise `none`, today's empty rendering. Load the seam with a dynamic import behind the `live` check, so fixture servers never evaluate it. The viewer's organization comes from membership on the server, read once per request by `readOrganizationSurface`. Platform URLs carry no organization.
6868

6969
The legacy organization layout redirects flagged organizations here through `lib/platform/routes.ts`. Link out to a legacy route only with `legacyRoutes`, which builds an absolute URL, and only to a route that mapper leaves unmapped. Linking to a mapped route makes the two surfaces redirect to each other.
7070

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { memo, type ReactNode } from 'react'
44
import { Streamdown } from 'streamdown'
55
import styles from '@/platform/app/(shell)/_components/chat/message/markdown.module.css'
66
import { CopyButton } from '@/platform/design-system/components'
7-
import { nodeText } from '@/platform/lib/data/node-text'
7+
import { extractTextContent } from '@/platform/lib/data/node-text'
88

99
const COMPONENTS = {
1010
p: ({ children }: { children?: ReactNode }) => <p data-type='T048'>{children}</p>,
@@ -21,7 +21,7 @@ const COMPONENTS = {
2121
pre: ({ children }: { children?: ReactNode }) => (
2222
<div className={styles.code}>
2323
<span className={styles.copy}>
24-
<CopyButton label='Copy code' text={nodeText(children)} />
24+
<CopyButton label='Copy code' text={extractTextContent(children)} />
2525
</span>
2626
<pre>{children}</pre>
2727
</div>

‎apps/sim/platform/app/(shell)/_components/session/chat-runtime.tsx‎

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

3-
import { createContext, type ReactNode, useContext, useMemo, useRef } from 'react'
3+
import { createContext, type ReactNode, useContext, useMemo, useRef, useState } from 'react'
44
import type { ChatRuntime } from '@/platform/app/(shell)/_components/chat/model'
55

66
type ChatHandlers = Pick<
@@ -22,7 +22,8 @@ const InteractionsContext = createContext<ChatInteractions | null>(null)
2222
/**
2323
* A runtime is rebuilt on every stream chunk, while a settled turn reads only handlers and what is
2424
* awaiting a decision. Handlers keep one identity and call the latest runtime, so a chunk reaches
25-
* the turn it changed rather than the whole transcript.
25+
* the turn it changed rather than the whole transcript. Which handlers a runtime offers is fixed
26+
* when it is built.
2627
*/
2728
export function ChatRuntimeProvider({
2829
runtime,
@@ -33,26 +34,18 @@ export function ChatRuntimeProvider({
3334
}) {
3435
const latest = useRef(runtime)
3536
latest.current = runtime
36-
const canRetry = Boolean(runtime.retry)
37-
const canRate = Boolean(runtime.rate)
38-
const canFork = Boolean(runtime.fork)
39-
const canOpenResource = Boolean(runtime.openResource)
40-
const canFinishHandoff = Boolean(runtime.finishTerminalHandoff)
41-
const handlers = useMemo<ChatHandlers>(
42-
() => ({
43-
sendText: (text) => latest.current.sendText(text),
44-
registerApproval: (toolCallId) => latest.current.registerApproval(toolCallId),
45-
decideTools: (ids, decision) => latest.current.decideTools(ids, decision),
46-
retry: canRetry ? (turnId) => latest.current.retry?.(turnId) : undefined,
47-
rate: canRate ? async (input) => latest.current.rate?.(input) : undefined,
48-
fork: canFork ? async (turnId) => latest.current.fork?.(turnId) : undefined,
49-
openResource: canOpenResource ? (ref) => latest.current.openResource?.(ref) : undefined,
50-
finishTerminalHandoff: canFinishHandoff
51-
? (terminalId) => latest.current.finishTerminalHandoff?.(terminalId)
52-
: undefined,
53-
}),
54-
[canRetry, canRate, canFork, canOpenResource, canFinishHandoff]
55-
)
37+
const [handlers] = useState<ChatHandlers>(() => ({
38+
sendText: (text) => latest.current.sendText(text),
39+
registerApproval: (toolCallId) => latest.current.registerApproval(toolCallId),
40+
decideTools: (ids, decision) => latest.current.decideTools(ids, decision),
41+
retry: runtime.retry && ((turnId) => latest.current.retry?.(turnId)),
42+
rate: runtime.rate && (async (input) => latest.current.rate?.(input)),
43+
fork: runtime.fork && (async (turnId) => latest.current.fork?.(turnId)),
44+
openResource: runtime.openResource && ((ref) => latest.current.openResource?.(ref)),
45+
finishTerminalHandoff:
46+
runtime.finishTerminalHandoff &&
47+
((terminalId) => latest.current.finishTerminalHandoff?.(terminalId)),
48+
}))
5649
const { approvals, review } = runtime
5750
const interactions = useMemo(() => ({ approvals, review }), [approvals, review])
5851
return (

‎apps/sim/platform/app/(shell)/people/_components/directory-sync/directory-sync-modal.tsx‎

Lines changed: 8 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -317,18 +317,15 @@ export function DirectorySyncModal({ organization, onClose }: DirectorySyncModal
317317
label: string
318318
disabled: boolean
319319
destructive?: boolean
320-
confirm: () => false
320+
confirm: () => void
321321
} => {
322322
if (mode === 'disconnect' && sync)
323323
return {
324324
title: 'Disconnect directory',
325325
label: 'Disconnect',
326326
disabled: directory.pending,
327327
destructive: true,
328-
confirm: () => {
329-
directory.disconnect(backToDetails)
330-
return false
331-
},
328+
confirm: () => directory.disconnect(backToDetails),
332329
}
333330
if (connecting)
334331
return {
@@ -342,17 +339,13 @@ export function DirectorySyncModal({ organization, onClose }: DirectorySyncModal
342339
confirm: () => {
343340
const errors = directory.connect({ ...draft, source }, backToDetails)
344341
setFieldErrors(errors ?? {})
345-
return false
346342
},
347343
}
348344
return {
349345
title: 'Directory sync',
350346
label: syncLabel(sync, syncing),
351347
disabled: sync.state === 'paused' || syncing || sync.retryBlocked,
352-
confirm: () => {
353-
directory.run()
354-
return false
355-
},
348+
confirm: directory.run,
356349
}
357350
})()
358351

@@ -364,9 +357,11 @@ export function DirectorySyncModal({ organization, onClose }: DirectorySyncModal
364357
confirmLabel={label}
365358
confirmDisabled={disabled}
366359
destructive={destructive}
367-
cancelLabel={steppedBack ? 'Back' : undefined}
368-
onCancel={steppedBack ? backToDetails : undefined}
369-
onConfirm={confirm}
360+
onBack={steppedBack ? backToDetails : undefined}
361+
onConfirm={() => {
362+
confirm()
363+
return false
364+
}}
370365
>
371366
{directory.loading ? (
372367
<p role='status' data-type='T060'>

‎apps/sim/platform/app/(shell)/people/_components/import/import-modal.tsx‎

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -142,8 +142,7 @@ export function ImportModal({ organization, onClose }: ImportModalProps) {
142142
title={title}
143143
confirmLabel={done ? 'Done' : preview ? 'Import' : 'Preview import'}
144144
confirmDisabled={pending || (!preview && !ready)}
145-
cancelLabel={preview ? 'Back' : undefined}
146-
onCancel={preview && !pending ? () => setPreview(null) : undefined}
145+
onBack={preview ? () => setPreview(null) : undefined}
147146
onConfirm={
148147
done
149148
? undefined
Lines changed: 3 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,19 @@
11
import type { ReactNode } from 'react'
22
import styles from '@/platform/app/(shell)/people/_components/people-page/people-page.module.css'
3-
import { PeopleStatus } from '@/platform/app/(shell)/people/_components/people-page/people-status'
43

54
interface PeopleFrameProps {
6-
title?: string
75
actions?: ReactNode
8-
/** Replaces the page body while the route's first read is pending, in the same geometry. */
9-
loading?: string
106
children?: ReactNode
117
}
128

13-
export function PeopleFrame({ title = 'People', actions, loading, children }: PeopleFrameProps) {
9+
export function PeopleFrame({ actions, children }: PeopleFrameProps) {
1410
return (
1511
<main className={styles.page}>
1612
<header className={styles.header}>
17-
<h1 data-type='T076'>{title}</h1>
13+
<h1 data-type='T076'>People</h1>
1814
{actions && <div className={styles.actions}>{actions}</div>}
1915
</header>
20-
{loading ? (
21-
<>
22-
<div className={styles.viewsPlaceholder} aria-hidden='true' />
23-
<PeopleStatus>{loading}</PeopleStatus>
24-
</>
25-
) : (
26-
children
27-
)}
16+
{children}
2817
</main>
2918
)
3019
}

0 commit comments

Comments
 (0)