Skip to content

Commit 8d74e35

Browse files
committed
improvement(scheduled-tasks): render prompt chips in task details and align weekday picker
The read-only task-details modal dumped the stored prompt into a plain textarea, so a previously entered prompt showed raw `@Gmail` / `/skill` text instead of the chips the editor renders while composing. Reuse the home `PromptEditor` in a new read-only mode (textarea becomes `readOnly`, caret-anchored menus unmount, the chip overlay still paints) seeded with the task's prompt and contexts, so the record renders mention/skill chips identically to the create/edit modal. Also realign the weekly "Repeat on" day picker: instead of a stretched segmented bar with a faint `active` surface, render a seven-column grid of day cells that reuse the calendar's exact day-cell grammar (`primary` fill when on, bare `--text-body` when off) so it reads as a one-row sibling of the date picker.
1 parent 4bf7917 commit 8d74e35

3 files changed

Lines changed: 116 additions & 50 deletions

File tree

apps/sim/app/workspace/[workspaceId]/home/components/user-input/components/prompt-editor/prompt-editor.tsx

Lines changed: 41 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,14 @@ export interface PromptEditorProps extends PromptEditorKeyPolicy {
2727
placeholder?: string
2828
/** Focuses the editor (caret at end) on mount. */
2929
autoFocus?: boolean
30+
/**
31+
* Renders the editor as a non-editable display surface: the textarea becomes
32+
* `readOnly` (so the chip overlay still paints `@`-mention / `/`-skill chips
33+
* and the text stays selectable/copyable) and the caret-anchored resource and
34+
* skill menus are not mounted. Use for read-only records — e.g. a finished
35+
* scheduled task — where the prompt should render with chips but not be edited.
36+
*/
37+
readOnly?: boolean
3038
/**
3139
* Layout/sizing only — a height cap (`max-h-[200px]`) or fill (`flex-1`)
3240
* for the scroll container. The text chrome is owned by the editor.
@@ -56,6 +64,7 @@ export function PromptEditor({
5664
editor,
5765
placeholder,
5866
autoFocus = false,
67+
readOnly = false,
5968
className,
6069
'aria-label': ariaLabel,
6170
onSubmit,
@@ -73,7 +82,7 @@ export function PromptEditor({
7382
}, [value, textareaRef])
7483

7584
useEffect(() => {
76-
if (autoFocus) editor.focusAtEnd()
85+
if (autoFocus && !readOnly) editor.focusAtEnd()
7786
// eslint-disable-next-line react-hooks/exhaustive-deps -- mount-only focus
7887
}, [])
7988

@@ -167,38 +176,45 @@ export function PromptEditor({
167176
<textarea
168177
ref={textareaRef}
169178
value={value}
170-
onChange={editor.handleInputChange}
171-
onKeyDown={(e) => editor.handleKeyDown(e, { onSubmit, onArrowUpOnEmpty })}
172-
onPaste={editor.handlePaste}
179+
readOnly={readOnly}
180+
onChange={readOnly ? undefined : editor.handleInputChange}
181+
onKeyDown={
182+
readOnly ? undefined : (e) => editor.handleKeyDown(e, { onSubmit, onArrowUpOnEmpty })
183+
}
184+
onPaste={readOnly ? undefined : editor.handlePaste}
173185
onCopy={editor.handleCopy}
174-
onCut={editor.handleCut}
175-
onSelect={editor.handleSelectAdjust}
176-
onMouseUp={editor.handleSelectAdjust}
186+
onCut={readOnly ? undefined : editor.handleCut}
187+
onSelect={readOnly ? undefined : editor.handleSelectAdjust}
188+
onMouseUp={readOnly ? undefined : editor.handleSelectAdjust}
177189
placeholder={placeholder}
178190
aria-label={ariaLabel}
179191
rows={1}
180192
className={TEXTAREA_BASE_CLASSES}
181193
/>
182194
</div>
183195

184-
<PlusMenuDropdown
185-
ref={editor.plusMenuRef}
186-
availableResources={editor.availableResources}
187-
onResourceSelect={editor.insertResource}
188-
onClose={editor.handlePlusMenuClose}
189-
textareaRef={editor.textareaRef}
190-
pendingCursorRef={editor.pendingCursorRef}
191-
mentionQuery={editor.mentionQuery ?? undefined}
192-
/>
193-
<SkillsMenuDropdown
194-
ref={editor.skillsMenuRef}
195-
skills={editor.skills}
196-
onSkillSelect={editor.handleSkillSelect}
197-
onClose={editor.handleSkillsMenuClose}
198-
textareaRef={editor.textareaRef}
199-
pendingCursorRef={editor.pendingCursorRef}
200-
slashQuery={editor.slashQuery ?? undefined}
201-
/>
196+
{!readOnly && (
197+
<>
198+
<PlusMenuDropdown
199+
ref={editor.plusMenuRef}
200+
availableResources={editor.availableResources}
201+
onResourceSelect={editor.insertResource}
202+
onClose={editor.handlePlusMenuClose}
203+
textareaRef={editor.textareaRef}
204+
pendingCursorRef={editor.pendingCursorRef}
205+
mentionQuery={editor.mentionQuery ?? undefined}
206+
/>
207+
<SkillsMenuDropdown
208+
ref={editor.skillsMenuRef}
209+
skills={editor.skills}
210+
onSkillSelect={editor.handleSkillSelect}
211+
onClose={editor.handleSkillsMenuClose}
212+
textareaRef={editor.textareaRef}
213+
pendingCursorRef={editor.pendingCursorRef}
214+
slashQuery={editor.slashQuery ?? undefined}
215+
/>
216+
</>
217+
)}
202218
</div>
203219
)
204220
}

apps/sim/app/workspace/[workspaceId]/scheduled-tasks/components/task-details-modal/task-details-modal.tsx

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

3+
import { useEffect } from 'react'
34
import { format } from 'date-fns'
5+
import { useParams } from 'next/navigation'
46
import {
57
Calendar,
68
ChipModal,
79
ChipModalBody,
810
ChipModalField,
911
ChipModalFooter,
1012
ChipModalHeader,
13+
chipFieldSurfaceClass,
1114
} from '@/components/emcn'
15+
import { cn } from '@/lib/core/utils/cn'
16+
import {
17+
PromptEditor,
18+
usePromptEditor,
19+
} from '@/app/workspace/[workspaceId]/home/components/user-input/components'
1220
import type {
1321
ScheduledTask,
1422
ScheduledTaskStatus,
@@ -35,7 +43,7 @@ interface TaskDetailsModalProps {
3543
/**
3644
* Read-only record modal for tasks that are running or already finished —
3745
* pending tasks open the edit `TaskModal` instead. Three plaintext fields:
38-
* Status and the run time as copy fields, the prompt as a view-only textarea.
46+
* Status and the run time as copy fields, the prompt as a view-only chip editor.
3947
*/
4048
export function TaskDetailsModal({ task, onClose }: TaskDetailsModalProps) {
4149
return (
@@ -47,23 +55,51 @@ export function TaskDetailsModal({ task, onClose }: TaskDetailsModalProps) {
4755
size='md'
4856
srTitle='Scheduled task'
4957
>
50-
{task && (
51-
<>
52-
<ChipModalHeader icon={Calendar} onClose={onClose}>
53-
Scheduled task
54-
</ChipModalHeader>
55-
<ChipModalBody>
56-
<ChipModalField type='copy' title='Status' value={STATUS_COPY[task.status].label} />
57-
<ChipModalField
58-
type='copy'
59-
title={STATUS_COPY[task.status].timeTitle}
60-
value={format(task.runAt, "EEEE, MMMM d, yyyy 'at' h:mm a")}
61-
/>
62-
<ChipModalField type='textarea' title='Prompt' value={task.prompt} viewOnly />
63-
</ChipModalBody>
64-
<ChipModalFooter onCancel={onClose} primaryAction={{ label: 'Done', onClick: onClose }} />
65-
</>
66-
)}
58+
{task && <TaskDetailsContent task={task} onClose={onClose} />}
6759
</ChipModal>
6860
)
6961
}
62+
63+
/**
64+
* Inner content, mounted only while a task is shown (the Radix portal unmounts
65+
* closed content). Holding the read-only editor here keeps its mention-data
66+
* queries from firing on page load and re-seeds from the task on each open.
67+
*/
68+
function TaskDetailsContent({ task, onClose }: { task: ScheduledTask; onClose: () => void }) {
69+
const { workspaceId } = useParams<{ workspaceId: string }>()
70+
const editor = usePromptEditor({ workspaceId, initialValue: task.prompt })
71+
const setContexts = editor.setContexts
72+
73+
/**
74+
* Re-registers the task's stored `@`-mentions once on open so resource chips
75+
* (files, tables, knowledge) render. Integration `@`-mentions and `/`-skills
76+
* chipify from the seeded text alone, so they render even without stored
77+
* contexts. Runs once per open since the content remounts each time it opens.
78+
*/
79+
useEffect(() => {
80+
if (task.contexts && task.contexts.length > 0) setContexts(task.contexts)
81+
// eslint-disable-next-line react-hooks/exhaustive-deps -- mount-only re-register
82+
}, [])
83+
84+
return (
85+
<>
86+
<ChipModalHeader icon={Calendar} onClose={onClose}>
87+
Scheduled task
88+
</ChipModalHeader>
89+
<ChipModalBody>
90+
<ChipModalField type='copy' title='Status' value={STATUS_COPY[task.status].label} />
91+
<ChipModalField
92+
type='copy'
93+
title={STATUS_COPY[task.status].timeTitle}
94+
value={format(task.runAt, "EEEE, MMMM d, yyyy 'at' h:mm a")}
95+
/>
96+
<ChipModalField type='custom' title='Prompt'>
97+
<div className={cn(chipFieldSurfaceClass, 'max-h-[200px] overflow-y-auto px-1 py-0.5')}>
98+
<PromptEditor editor={editor} readOnly aria-label='Prompt' />
99+
</div>
100+
</ChipModalField>
101+
</ChipModalBody>
102+
<ChipModalFooter onCancel={onClose} primaryAction={{ label: 'Done', onClick: onClose }} />
103+
</>
104+
)
105+
}

apps/sim/app/workspace/[workspaceId]/scheduled-tasks/components/task-modal/recurrence-section.tsx

Lines changed: 21 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,14 @@
22

33
import { useRef } from 'react'
44
import { format } from 'date-fns'
5-
import { Chip, ChipDatePicker, ChipModalField, ChipModalSeparator, Switch } from '@/components/emcn'
5+
import {
6+
ChipDatePicker,
7+
ChipModalField,
8+
ChipModalSeparator,
9+
chipVariants,
10+
Switch,
11+
} from '@/components/emcn'
12+
import { cn } from '@/lib/core/utils/cn'
613
import type {
714
MonthlyMode,
815
Recurrence,
@@ -219,21 +226,28 @@ export function RecurrenceSection({ recurrence, onChange, launchDate }: Recurren
219226

220227
{recurrence.frequency === 'weekly' && (
221228
<ChipModalField type='custom' title='Repeat on'>
222-
<div className='flex gap-1'>
229+
{/* A one-row extract of the calendar: seven equal day cells that
230+
reuse its exact day-cell grammar (`primary` fill when on, bare
231+
`--text-body` when off) so the weekday toggles read as a sibling
232+
of the date picker rather than a separate segmented bar. */}
233+
<div className='grid grid-cols-7 gap-1'>
223234
{WEEKDAYS.map((weekday) => {
224235
const selected = selectedWeekdays.includes(weekday.value)
225236
return (
226-
<Chip
237+
<button
227238
key={weekday.value}
228-
active={selected}
229-
flush
230-
className='min-w-0 flex-1 justify-center'
239+
type='button'
231240
aria-pressed={selected}
232241
aria-label={weekday.name}
233242
onClick={() => handleWeekdayToggle(weekday.value)}
243+
className={cn(
244+
chipVariants({ variant: selected ? 'primary' : undefined, flush: true }),
245+
'h-[30px] w-full justify-center p-0',
246+
!selected && 'text-[var(--text-body)]'
247+
)}
234248
>
235249
{weekday.short}
236-
</Chip>
250+
</button>
237251
)
238252
})}
239253
</div>

0 commit comments

Comments
 (0)