Skip to content

Commit 802db5b

Browse files
fix(table): no typewriter flash; Run-row skips completed workflows
- Typewriter: reset the revealed text synchronously during render when the value changes (not in an effect), so a cell going from running→value no longer flashes the full text for one frame before animating. - Run row / manual incomplete runs now treat a `completed` group as done even if an output column is blank — only "Run all" re-runs completed cells. The auto cascade keeps re-filling blank outputs (completedAndFilled). Client optimistic stamp mirrors: incomplete skips `completed` cells.
1 parent 4445e31 commit 802db5b

3 files changed

Lines changed: 29 additions & 23 deletions

File tree

apps/sim/app/workspace/[workspaceId]/tables/[tableId]/components/table-grid/cells/cell-render.tsx

Lines changed: 17 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -303,33 +303,35 @@ const TYPEWRITER_MS_PER_CHAR = 15
303303
*/
304304
function useTypewriter(text: string | null): string | null {
305305
const [revealed, setRevealed] = useState<string | null>(text)
306-
const isFirstRunRef = useRef(true)
307306
const prevTextRef = useRef<string | null>(text)
307+
const mountedRef = useRef(false)
308+
const animateRef = useRef(false)
308309

309-
useEffect(() => {
310-
if (isFirstRunRef.current) {
311-
isFirstRunRef.current = false
312-
prevTextRef.current = text
313-
setRevealed(text)
314-
return
315-
}
316-
if (prevTextRef.current === text) return
310+
// Reset synchronously during render when `text` changes (not on first mount)
311+
// so no frame ever shows the full new value before the animation begins —
312+
// an effect-based reset lands one frame late and flashes the whole text.
313+
if (prevTextRef.current !== text) {
317314
prevTextRef.current = text
315+
const animate = mountedRef.current && text !== null && text.length > 0
316+
animateRef.current = animate
317+
setRevealed(animate ? '' : text)
318+
}
318319

319-
if (text === null || text.length === 0) {
320-
setRevealed(text)
321-
return
322-
}
320+
useEffect(() => {
321+
mountedRef.current = true
322+
}, [])
323323

324-
const full = text
324+
useEffect(() => {
325+
if (!animateRef.current) return
326+
animateRef.current = false
327+
const full = text as string
325328
const start = performance.now()
326329
let raf = 0
327330
const tick = (now: number) => {
328331
const chars = Math.min(full.length, Math.floor((now - start) / TYPEWRITER_MS_PER_CHAR))
329332
setRevealed(full.slice(0, chars))
330333
if (chars < full.length) raf = requestAnimationFrame(tick)
331334
}
332-
setRevealed('')
333335
raf = requestAnimationFrame(tick)
334336
return () => cancelAnimationFrame(raf)
335337
}, [text])

apps/sim/hooks/queries/tables.ts

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,6 @@ import type {
7373
} from '@/lib/table'
7474
import {
7575
areGroupDepsSatisfied,
76-
areOutputsFilled,
7776
isExecInFlight,
7877
optimisticallyScheduleNewlyEligibleGroups,
7978
} from '@/lib/table/deps'
@@ -1418,12 +1417,10 @@ export function useRunColumn({ workspaceId, tableId }: RowMutationContext) {
14181417
// dispatcher regardless of mode. Stamping pending here would leave
14191418
// the cell flashing Queued indefinitely (no SSE event will arrive).
14201419
if (group && !areGroupDepsSatisfied(group, r)) continue
1421-
// Mirror server eligibility for `mode: 'incomplete'`: skip cells whose
1422-
// outputs are filled, regardless of exec status. A cancelled/error
1423-
// cell with a leftover value from a prior run was rendering as filled
1424-
// but flipping to "queued" optimistically here even though the server
1425-
// would skip it.
1426-
if (runMode === 'incomplete' && group && areOutputsFilled(group, r)) continue
1420+
// Mirror server eligibility for manual `mode: 'incomplete'`: a
1421+
// `completed` group is done (even with a blank output) — only "Run
1422+
// all" re-runs it. error/cancelled/never-run cells still re-run.
1423+
if (runMode === 'incomplete' && exec?.status === 'completed') continue
14271424
next[groupId] = buildPendingExec(exec)
14281425
// Mirror the server-side bulk clear: wipe output values so the cell
14291426
// doesn't render the stale completed value behind a pending badge.

apps/sim/lib/table/workflow-columns.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,14 @@ export function classifyEligibility(
9393
if (!isManualRun && completedAndFilled) return 'completed-on-auto'
9494
if (!isManualRun && status === 'error') return 'error-on-auto'
9595
if (!isManualRun && status === 'cancelled') return 'cancelled-on-auto'
96-
if (mode === 'incomplete' && completedAndFilled) return 'completed-on-incomplete'
96+
// Manual incomplete-mode runs (Run row / Run incomplete) treat a `completed`
97+
// group as done even if an output is blank — only "Run all" re-runs it. The
98+
// auto cascade still re-fills blank outputs (completedAndFilled).
99+
if (mode === 'incomplete') {
100+
if (isManualRun ? status === 'completed' : completedAndFilled) {
101+
return 'completed-on-incomplete'
102+
}
103+
}
97104

98105
if (isManualRun && group.autoRun === false) return 'manual-bypass'
99106
return areGroupDepsSatisfied(group, row) ? 'eligible' : 'deps-unmet'

0 commit comments

Comments
 (0)